Merge "Bluetooth 5 AdvertisingSet implementation (1/4)"
This commit is contained in:
@@ -50,14 +50,6 @@ interface IBluetoothGatt {
|
||||
void stopScan(in int scannerId);
|
||||
void flushPendingBatchResults(in int scannerId);
|
||||
|
||||
void registerAdvertiser(in IAdvertiserCallback callback);
|
||||
void unregisterAdvertiser(in int advertiserId);
|
||||
void startMultiAdvertising(in int advertiserId,
|
||||
in AdvertiseData advertiseData,
|
||||
in AdvertiseData scanResponse,
|
||||
in AdvertiseSettings settings);
|
||||
void stopMultiAdvertising(in int advertiserId);
|
||||
|
||||
void startAdvertisingSet(in AdvertisingSetParameters parameters, in AdvertiseData advertiseData,
|
||||
in AdvertiseData scanResponse, in PeriodicAdvertisingParameters periodicParameters,
|
||||
in AdvertiseData periodicData, in IAdvertisingSetCallback callback);
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.os.ParcelUuid;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
@@ -60,11 +61,12 @@ public final class BluetoothLeAdvertiser {
|
||||
private final IBluetoothManager mBluetoothManager;
|
||||
private final Handler mHandler;
|
||||
private BluetoothAdapter mBluetoothAdapter;
|
||||
private final Map<AdvertiseCallback, AdvertiseCallbackWrapper>
|
||||
mLeAdvertisers = new HashMap<AdvertiseCallback, AdvertiseCallbackWrapper>();
|
||||
private final Map<AdvertiseCallback, AdvertisingSetCallback>
|
||||
mLegacyAdvertisers = new HashMap<>();
|
||||
private final Map<AdvertisingSetCallback, IAdvertisingSetCallback>
|
||||
advertisingSetCallbackWrappers = new HashMap<>();
|
||||
private final Map<Integer, AdvertisingSet> advertisingSets = new HashMap<>();
|
||||
mCallbackWrappers = Collections.synchronizedMap(new HashMap<>());
|
||||
private final Map<Integer, AdvertisingSet>
|
||||
mAdvertisingSets = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
/**
|
||||
* Use BluetoothAdapter.getLeAdvertiser() instead.
|
||||
@@ -109,7 +111,7 @@ public final class BluetoothLeAdvertiser {
|
||||
public void startAdvertising(AdvertiseSettings settings,
|
||||
AdvertiseData advertiseData, AdvertiseData scanResponse,
|
||||
final AdvertiseCallback callback) {
|
||||
synchronized (mLeAdvertisers) {
|
||||
synchronized (mLegacyAdvertisers) {
|
||||
BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback cannot be null");
|
||||
@@ -120,25 +122,65 @@ public final class BluetoothLeAdvertiser {
|
||||
postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
|
||||
return;
|
||||
}
|
||||
if (mLeAdvertisers.containsKey(callback)) {
|
||||
if (mLegacyAdvertisers.containsKey(callback)) {
|
||||
postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
|
||||
return;
|
||||
}
|
||||
|
||||
IBluetoothGatt gatt;
|
||||
try {
|
||||
gatt = mBluetoothManager.getBluetoothGatt();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
|
||||
postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
|
||||
return;
|
||||
AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder();
|
||||
parameters.setLegacyMode(true);
|
||||
parameters.setConnectable(isConnectable);
|
||||
parameters.setTimeout(settings.getTimeout());
|
||||
if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) {
|
||||
parameters.setInterval(1600); // 1s
|
||||
} else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) {
|
||||
parameters.setInterval(400); // 250ms
|
||||
} else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) {
|
||||
parameters.setInterval(160); // 100ms
|
||||
}
|
||||
AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData,
|
||||
scanResponse, settings, gatt);
|
||||
wrapper.startRegisteration();
|
||||
|
||||
if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW) {
|
||||
parameters.setTxPowerLevel(-21);
|
||||
} else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_LOW) {
|
||||
parameters.setTxPowerLevel(-15);
|
||||
} else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) {
|
||||
parameters.setTxPowerLevel(-7);
|
||||
} else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) {
|
||||
parameters.setTxPowerLevel(1);
|
||||
}
|
||||
|
||||
AdvertisingSetCallback wrapped = wrapOldCallback(callback, settings);
|
||||
mLegacyAdvertisers.put(callback, wrapped);
|
||||
startAdvertisingSet(parameters.build(), advertiseData, scanResponse, null, null,
|
||||
wrapped);
|
||||
}
|
||||
}
|
||||
|
||||
AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) {
|
||||
return new AdvertisingSetCallback() {
|
||||
public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int status) {
|
||||
if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
|
||||
postStartFailure(callback, status);
|
||||
return;
|
||||
}
|
||||
|
||||
postStartSuccess(callback, settings);
|
||||
}
|
||||
|
||||
/* Legacy advertiser is disabled on timeout */
|
||||
public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) {
|
||||
if (enabled == true) {
|
||||
Log.e(TAG, "Legacy advertiser should be only disabled on timeout," +
|
||||
" but was enabled!");
|
||||
return;
|
||||
}
|
||||
|
||||
stopAdvertising(callback);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
|
||||
* {@link BluetoothLeAdvertiser#startAdvertising}.
|
||||
@@ -148,20 +190,21 @@ public final class BluetoothLeAdvertiser {
|
||||
* @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
|
||||
*/
|
||||
public void stopAdvertising(final AdvertiseCallback callback) {
|
||||
synchronized (mLeAdvertisers) {
|
||||
synchronized (mLegacyAdvertisers) {
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback cannot be null");
|
||||
}
|
||||
AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback);
|
||||
AdvertisingSetCallback wrapper = mLegacyAdvertisers.get(callback);
|
||||
if (wrapper == null) return;
|
||||
wrapper.stopAdvertising();
|
||||
|
||||
stopAdvertisingSet(wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new advertising set. If operation succeed, device will start advertising. This
|
||||
* method returns immediately, the operation status is delivered through
|
||||
* {@code callback.onNewAdvertisingSet()}.
|
||||
* {@code callback.onAdvertisingSetStarted()}.
|
||||
* <p>
|
||||
* @param parameters advertising set parameters.
|
||||
* @param advertiseData Advertisement data to be broadcasted.
|
||||
@@ -180,7 +223,7 @@ public final class BluetoothLeAdvertiser {
|
||||
/**
|
||||
* Creates a new advertising set. If operation succeed, device will start advertising. This
|
||||
* method returns immediately, the operation status is delivered through
|
||||
* {@code callback.onNewAdvertisingSet()}.
|
||||
* {@code callback.onAdvertisingSetStarted()}.
|
||||
* <p>
|
||||
* @param parameters advertising set parameters.
|
||||
* @param advertiseData Advertisement data to be broadcasted.
|
||||
@@ -209,7 +252,10 @@ public final class BluetoothLeAdvertiser {
|
||||
}
|
||||
|
||||
IAdvertisingSetCallback wrapped = wrap(callback, handler);
|
||||
advertisingSetCallbackWrappers.put(callback, wrapped);
|
||||
if (mCallbackWrappers.putIfAbsent(callback, wrapped) != null) {
|
||||
throw new IllegalArgumentException(
|
||||
"callback instance already associated with advertising");
|
||||
}
|
||||
|
||||
try {
|
||||
gatt.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
|
||||
@@ -229,10 +275,9 @@ public final class BluetoothLeAdvertiser {
|
||||
throw new IllegalArgumentException("callback cannot be null");
|
||||
}
|
||||
|
||||
IAdvertisingSetCallback wrapped = advertisingSetCallbackWrappers.remove(callback);
|
||||
IAdvertisingSetCallback wrapped = mCallbackWrappers.remove(callback);
|
||||
if (wrapped == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"callback does not represent valid registered callback.");
|
||||
return;
|
||||
}
|
||||
|
||||
IBluetoothGatt gatt;
|
||||
@@ -251,7 +296,9 @@ public final class BluetoothLeAdvertiser {
|
||||
* @hide
|
||||
*/
|
||||
public void cleanup() {
|
||||
mLeAdvertisers.clear();
|
||||
mLegacyAdvertisers.clear();
|
||||
mCallbackWrappers.clear();
|
||||
mAdvertisingSets.clear();
|
||||
}
|
||||
|
||||
// Compute the size of advertisement data or scan resp
|
||||
@@ -317,13 +364,13 @@ public final class BluetoothLeAdvertiser {
|
||||
public void run() {
|
||||
if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
|
||||
callback.onAdvertisingSetStarted(null, status);
|
||||
advertisingSetCallbackWrappers.remove(callback);
|
||||
mCallbackWrappers.remove(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
AdvertisingSet advertisingSet =
|
||||
new AdvertisingSet(advertiserId, mBluetoothManager);
|
||||
advertisingSets.put(advertiserId, advertisingSet);
|
||||
mAdvertisingSets.put(advertiserId, advertisingSet);
|
||||
callback.onAdvertisingSetStarted(advertisingSet, status);
|
||||
}
|
||||
});
|
||||
@@ -333,10 +380,10 @@ public final class BluetoothLeAdvertiser {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AdvertisingSet advertisingSet = advertisingSets.get(advertiserId);
|
||||
AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
|
||||
callback.onAdvertisingSetStopped(advertisingSet);
|
||||
advertisingSets.remove(advertiserId);
|
||||
advertisingSetCallbackWrappers.remove(callback);
|
||||
mAdvertisingSets.remove(advertiserId);
|
||||
mCallbackWrappers.remove(callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -345,7 +392,7 @@ public final class BluetoothLeAdvertiser {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AdvertisingSet advertisingSet = advertisingSets.get(advertiserId);
|
||||
AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
|
||||
callback.onAdvertisingEnabled(advertisingSet, enabled, status);
|
||||
}
|
||||
});
|
||||
@@ -355,7 +402,7 @@ public final class BluetoothLeAdvertiser {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AdvertisingSet advertisingSet = advertisingSets.get(advertiserId);
|
||||
AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
|
||||
callback.onAdvertisingDataSet(advertisingSet, status);
|
||||
}
|
||||
});
|
||||
@@ -365,7 +412,7 @@ public final class BluetoothLeAdvertiser {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AdvertisingSet advertisingSet = advertisingSets.get(advertiserId);
|
||||
AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
|
||||
callback.onScanResponseDataSet(advertisingSet, status);
|
||||
}
|
||||
});
|
||||
@@ -375,7 +422,7 @@ public final class BluetoothLeAdvertiser {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AdvertisingSet advertisingSet = advertisingSets.get(advertiserId);
|
||||
AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
|
||||
callback.onAdvertisingParametersUpdated(advertisingSet, status);
|
||||
}
|
||||
});
|
||||
@@ -385,7 +432,7 @@ public final class BluetoothLeAdvertiser {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AdvertisingSet advertisingSet = advertisingSets.get(advertiserId);
|
||||
AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
|
||||
callback.onPeriodicAdvertisingParametersUpdated(advertisingSet, status);
|
||||
}
|
||||
});
|
||||
@@ -395,7 +442,7 @@ public final class BluetoothLeAdvertiser {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AdvertisingSet advertisingSet = advertisingSets.get(advertiserId);
|
||||
AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
|
||||
callback.onPeriodicAdvertisingDataSet(advertisingSet, status);
|
||||
}
|
||||
});
|
||||
@@ -405,7 +452,7 @@ public final class BluetoothLeAdvertiser {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AdvertisingSet advertisingSet = advertisingSets.get(advertiserId);
|
||||
AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
|
||||
callback.onPeriodicAdvertisingEnable(advertisingSet, enable, status);
|
||||
}
|
||||
});
|
||||
@@ -413,144 +460,6 @@ public final class BluetoothLeAdvertiser {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Bluetooth GATT interface callbacks for advertising.
|
||||
*/
|
||||
private class AdvertiseCallbackWrapper extends IAdvertiserCallback.Stub {
|
||||
private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
|
||||
private final AdvertiseCallback mAdvertiseCallback;
|
||||
private final AdvertiseData mAdvertisement;
|
||||
private final AdvertiseData mScanResponse;
|
||||
private final AdvertiseSettings mSettings;
|
||||
private final IBluetoothGatt mBluetoothGatt;
|
||||
|
||||
// mAdvertiserId -1: not registered
|
||||
// -2: advertise stopped or registration timeout
|
||||
// >=0: registered and advertising started
|
||||
private int mAdvertiserId;
|
||||
private boolean mIsAdvertising = false;
|
||||
private int registrationError = AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR;
|
||||
|
||||
public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
|
||||
AdvertiseData advertiseData, AdvertiseData scanResponse,
|
||||
AdvertiseSettings settings,
|
||||
IBluetoothGatt bluetoothGatt) {
|
||||
mAdvertiseCallback = advertiseCallback;
|
||||
mAdvertisement = advertiseData;
|
||||
mScanResponse = scanResponse;
|
||||
mSettings = settings;
|
||||
mBluetoothGatt = bluetoothGatt;
|
||||
mAdvertiserId = -1;
|
||||
}
|
||||
|
||||
public void startRegisteration() {
|
||||
synchronized (this) {
|
||||
if (mAdvertiserId == -2) return;
|
||||
|
||||
try {
|
||||
mBluetoothGatt.registerAdvertiser(this);
|
||||
wait(LE_CALLBACK_TIMEOUT_MILLIS);
|
||||
} catch (InterruptedException | RemoteException e) {
|
||||
Log.e(TAG, "Failed to start registeration", e);
|
||||
}
|
||||
if (mAdvertiserId >= 0 && mIsAdvertising) {
|
||||
mLeAdvertisers.put(mAdvertiseCallback, this);
|
||||
} else if (mAdvertiserId < 0) {
|
||||
|
||||
// Registration timeout, reset mClientIf to -2 so no subsequent operations can
|
||||
// proceed.
|
||||
if (mAdvertiserId == -1) mAdvertiserId = -2;
|
||||
// Post internal error if registration failed.
|
||||
postStartFailure(mAdvertiseCallback, registrationError);
|
||||
} else {
|
||||
// Unregister application if it's already registered but advertise failed.
|
||||
try {
|
||||
mBluetoothGatt.unregisterAdvertiser(mAdvertiserId);
|
||||
mAdvertiserId = -2;
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "remote exception when unregistering", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stopAdvertising() {
|
||||
synchronized (this) {
|
||||
try {
|
||||
mBluetoothGatt.stopMultiAdvertising(mAdvertiserId);
|
||||
wait(LE_CALLBACK_TIMEOUT_MILLIS);
|
||||
} catch (InterruptedException | RemoteException e) {
|
||||
Log.e(TAG, "Failed to stop advertising", e);
|
||||
}
|
||||
// Advertise callback should have been removed from LeAdvertisers when
|
||||
// onMultiAdvertiseCallback was called. In case onMultiAdvertiseCallback is never
|
||||
// invoked and wait timeout expires, remove callback here.
|
||||
if (mLeAdvertisers.containsKey(mAdvertiseCallback)) {
|
||||
mLeAdvertisers.remove(mAdvertiseCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Advertiser interface registered - app is ready to go
|
||||
*/
|
||||
@Override
|
||||
public void onAdvertiserRegistered(int status, int advertiserId) {
|
||||
Log.d(TAG, "onAdvertiserRegistered() - status=" + status + " advertiserId=" + advertiserId);
|
||||
synchronized (this) {
|
||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
try {
|
||||
if (mAdvertiserId == -2) {
|
||||
// Registration succeeds after timeout, unregister advertiser.
|
||||
mBluetoothGatt.unregisterAdvertiser(advertiserId);
|
||||
} else {
|
||||
mAdvertiserId = advertiserId;
|
||||
mBluetoothGatt.startMultiAdvertising(mAdvertiserId, mAdvertisement,
|
||||
mScanResponse, mSettings);
|
||||
}
|
||||
return;
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "failed to start advertising", e);
|
||||
}
|
||||
} else if (status == AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS) {
|
||||
registrationError = status;
|
||||
}
|
||||
// Registration failed.
|
||||
mAdvertiserId = -2;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMultiAdvertiseCallback(int status, boolean isStart,
|
||||
AdvertiseSettings settings) {
|
||||
synchronized (this) {
|
||||
if (isStart) {
|
||||
if (status == AdvertiseCallback.ADVERTISE_SUCCESS) {
|
||||
// Start success
|
||||
mIsAdvertising = true;
|
||||
postStartSuccess(mAdvertiseCallback, settings);
|
||||
} else {
|
||||
// Start failure.
|
||||
postStartFailure(mAdvertiseCallback, status);
|
||||
}
|
||||
} else {
|
||||
// unregister advertiser for stop.
|
||||
try {
|
||||
mBluetoothGatt.unregisterAdvertiser(mAdvertiserId);
|
||||
mAdvertiserId = -2;
|
||||
mIsAdvertising = false;
|
||||
mLeAdvertisers.remove(mAdvertiseCallback);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "remote exception when unregistering", e);
|
||||
}
|
||||
}
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void postStartFailure(final AdvertiseCallback callback, final int error) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user