blob: bc0cdfe0101811e13a0426e9c237056010c8cd60 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby.fastpair.provider;
import static com.google.common.io.BaseEncoding.base16;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.AdvertisingSet;
import android.bluetooth.le.AdvertisingSetCallback;
import android.bluetooth.le.AdvertisingSetParameters;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.nearby.fastpair.provider.utils.Logger;
import android.os.ParcelUuid;
import androidx.annotation.Nullable;
import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
/** Fast Pair advertiser taking advantage of new Android Oreo advertising features. */
public final class OreoFastPairAdvertiser implements FastPairAdvertiser {
private static final String TAG = "OreoFastPairAdvertiser";
private final Logger mLogger = new Logger(TAG);
private final FastPairSimulator mSimulator;
private final BluetoothLeAdvertiser mAdvertiser;
private final AdvertisingSetCallback mAdvertisingSetCallback;
private AdvertisingSet mAdvertisingSet;
public OreoFastPairAdvertiser(FastPairSimulator simulator) {
this.mSimulator = simulator;
this.mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
this.mAdvertisingSetCallback = new AdvertisingSetCallback() {
@Override
public void onAdvertisingSetStarted(
AdvertisingSet set, int txPower, int status) {
if (status == AdvertisingSetCallback.ADVERTISE_SUCCESS) {
mLogger.log("Advertising succeeded, advertising at %s dBm", txPower);
simulator.setIsAdvertising(true);
mAdvertisingSet = set;
mAdvertisingSet.getOwnAddress();
} else {
mLogger.log(
new IllegalStateException(),
"Advertising failed, error code=%d", status);
}
}
@Override
public void onAdvertisingDataSet(AdvertisingSet set, int status) {
if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
mLogger.log(
new IllegalStateException(),
"Updating advertisement failed, error code=%d",
status);
stopAdvertising();
}
}
// Callback for AdvertisingSet.getOwnAddress().
@Override
public void onOwnAddressRead(
AdvertisingSet set, int addressType, String address) {
if (!address.equals(simulator.getBleAddress())) {
mLogger.log(
"Read own BLE address=%s at %s",
address,
new SimpleDateFormat("HH:mm:ss:SSS", Locale.US)
.format(Calendar.getInstance().getTime()));
// Implicitly start the advertising once BLE address callback arrived.
simulator.setBleAddress(address);
}
}
};
}
@Override
public void startAdvertising(@Nullable byte[] serviceData) {
// To be informed that BLE address is rotated, we need to polling query it asynchronously.
if (mAdvertisingSet != null) {
mAdvertisingSet.getOwnAddress();
}
if (mSimulator.isDestroyed()) {
return;
}
if (serviceData == null) {
mLogger.log("Service data is null, stop advertising");
stopAdvertising();
return;
}
AdvertiseData data =
new AdvertiseData.Builder()
.addServiceData(new ParcelUuid(FastPairService.ID), serviceData)
.setIncludeTxPowerLevel(true)
.build();
mLogger.log("Advertising FE2C service data=%s", base16().encode(serviceData));
if (mAdvertisingSet != null) {
mAdvertisingSet.setAdvertisingData(data);
return;
}
stopAdvertising();
AdvertisingSetParameters parameters =
new AdvertisingSetParameters.Builder()
.setLegacyMode(true)
.setConnectable(true)
.setScannable(true)
.setInterval(AdvertisingSetParameters.INTERVAL_LOW)
.setTxPowerLevel(convertAdvertiseSettingsTxPower(mSimulator.getTxPower()))
.build();
mAdvertiser.startAdvertisingSet(parameters, data, null, null, null,
mAdvertisingSetCallback);
}
private static int convertAdvertiseSettingsTxPower(int txPower) {
switch (txPower) {
case AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW:
return AdvertisingSetParameters.TX_POWER_ULTRA_LOW;
case AdvertiseSettings.ADVERTISE_TX_POWER_LOW:
return AdvertisingSetParameters.TX_POWER_LOW;
case AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM:
return AdvertisingSetParameters.TX_POWER_MEDIUM;
default:
return AdvertisingSetParameters.TX_POWER_HIGH;
}
}
@Override
public void stopAdvertising() {
if (mSimulator.isDestroyed()) {
return;
}
mAdvertiser.stopAdvertisingSet(mAdvertisingSetCallback);
mAdvertisingSet = null;
mSimulator.setIsAdvertising(false);
}
}