Merge "Add status callback for start/stop advertising. Fixes b/13289050, b/13418851, also fixes 13418671."

This commit is contained in:
Wei Wang
2014-03-19 21:29:58 +00:00
committed by Android (Google) Code Review
4 changed files with 131 additions and 71 deletions

View File

@@ -19,7 +19,9 @@ package android.bluetooth;
import android.annotation.SdkConstant; import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context; import android.content.Context;
import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper;
import android.os.ParcelUuid; import android.os.ParcelUuid;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
@@ -179,43 +181,6 @@ public final class BluetoothAdapter {
public static final String EXTRA_DISCOVERABLE_DURATION = public static final String EXTRA_DISCOVERABLE_DURATION =
"android.bluetooth.adapter.extra.DISCOVERABLE_DURATION"; "android.bluetooth.adapter.extra.DISCOVERABLE_DURATION";
/**
* Activity Action: Show a system activity to request BLE advertising.<br>
* If the device is not doing BLE advertising, this activity will start BLE advertising for the
* device, otherwise it will continue BLE advertising using the current
* {@link BluetoothAdvScanData}. <br>
* Note this activity will also request the user to turn on Bluetooth if it's not currently
* enabled.
* @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_START_ADVERTISING =
"android.bluetooth.adapter.action.START_ADVERTISING";
/**
* Activity Action: Stop the current BLE advertising.
* @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_STOP_ADVERTISING =
"android.bluetooth.adapter.action.STOP_ADVERTISING";
/**
* Broadcast Action: Indicate BLE Advertising is started.
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_BLUETOOTH_ADVERTISING_STARTED =
"android.bluetooth.adapter.action.ADVERTISING_STARTED";
/**
* Broadcast Action: Indicated BLE Advertising is stopped.
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_BLUETOOTH_ADVERTISING_STOPPED =
"android.bluetooth.adapter.action.ADVERTISING_STOPPED";
/** /**
* Activity Action: Show a system activity that allows the user to turn on * Activity Action: Show a system activity that allows the user to turn on
* Bluetooth. * Bluetooth.
@@ -247,6 +212,22 @@ public final class BluetoothAdapter {
public static final String ACTION_SCAN_MODE_CHANGED = public static final String ACTION_SCAN_MODE_CHANGED =
"android.bluetooth.adapter.action.SCAN_MODE_CHANGED"; "android.bluetooth.adapter.action.SCAN_MODE_CHANGED";
/**
* Broadcast Action: Indicate BLE Advertising is started.
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_BLUETOOTH_ADVERTISING_STARTED =
"android.bluetooth.adapter.action.ADVERTISING_STARTED";
/**
* Broadcast Action: Indicated BLE Advertising is stopped.
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_BLUETOOTH_ADVERTISING_STOPPED =
"android.bluetooth.adapter.action.ADVERTISING_STOPPED";
/** /**
* Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED} * Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED}
* intents to request the current scan mode. Possible values are: * intents to request the current scan mode. Possible values are:
@@ -383,9 +364,27 @@ public final class BluetoothAdapter {
/** The profile is in disconnecting state */ /** The profile is in disconnecting state */
public static final int STATE_DISCONNECTING = 3; public static final int STATE_DISCONNECTING = 3;
/** States for Bluetooth LE advertising */
/** @hide */
public static final int STATE_ADVERTISE_STARTING = 0;
/** @hide */
public static final int STATE_ADVERTISE_STARTED = 1;
/** @hide */
public static final int STATE_ADVERTISE_STOPPING = 2;
/** @hide */
public static final int STATE_ADVERTISE_STOPPED = 3;
/**
* Force stopping advertising without callback in case the advertising app dies.
* @hide
*/
public static final int STATE_ADVERTISE_FORCE_STOPPING = 4;
/** @hide */ /** @hide */
public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager"; public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
/** @hide */
public static final int ADVERTISE_CALLBACK_SUCCESS = 0;
private static final int ADDRESS_LENGTH = 17; private static final int ADDRESS_LENGTH = 17;
/** /**
@@ -399,7 +398,9 @@ public final class BluetoothAdapter {
private final Map<LeScanCallback, GattCallbackWrapper> mLeScanClients; private final Map<LeScanCallback, GattCallbackWrapper> mLeScanClients;
private BluetoothAdvScanData mBluetoothAdvScanData = null; private BluetoothAdvScanData mBluetoothAdvScanData = null;
private GattCallbackWrapper mAdvertisingCallback; private GattCallbackWrapper mAdvertisingGattCallback;
private final Handler mHandler; // Handler to post the advertise callback to run on main thread.
private final Object mLock = new Object();
/** /**
* Get a handle to the default local Bluetooth adapter. * Get a handle to the default local Bluetooth adapter.
@@ -435,6 +436,7 @@ public final class BluetoothAdapter {
} catch (RemoteException e) {Log.e(TAG, "", e);} } catch (RemoteException e) {Log.e(TAG, "", e);}
mManagerService = managerService; mManagerService = managerService;
mLeScanClients = new HashMap<LeScanCallback, GattCallbackWrapper>(); mLeScanClients = new HashMap<LeScanCallback, GattCallbackWrapper>();
mHandler = new Handler(Looper.getMainLooper());
} }
/** /**
@@ -474,6 +476,7 @@ public final class BluetoothAdapter {
/** /**
* Returns a {@link BluetoothAdvScanData} object representing advertising data. * Returns a {@link BluetoothAdvScanData} object representing advertising data.
* Data will be reset when bluetooth service is turned off.
* @hide * @hide
*/ */
public BluetoothAdvScanData getAdvScanData() { public BluetoothAdvScanData getAdvScanData() {
@@ -494,19 +497,34 @@ public final class BluetoothAdapter {
} }
} }
/**
* Interface for BLE advertising callback.
*
* @hide
*/
public interface AdvertiseCallback {
/**
* Callback when advertise starts.
* @param status - {@link #ADVERTISE_CALLBACK_SUCCESS} for success, others for failure.
*/
void onAdvertiseStart(int status);
/**
* Callback when advertise stops.
* @param status - {@link #ADVERTISE_CALLBACK_SUCCESS} for success, others for failure.
*/
void onAdvertiseStop(int status);
}
/** /**
* Start BLE advertising using current {@link BluetoothAdvScanData}. * Start BLE advertising using current {@link BluetoothAdvScanData}.
* An app should start advertising by requesting
* {@link BluetoothAdapter#ACTION_START_ADVERTISING} instead of calling this method directly.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}
* *
* @return true if BLE avertising succeeds, false otherwise. * @param callback - {@link AdvertiseCallback}
* @return true if BLE advertising succeeds, false otherwise.
* @hide * @hide
*/ */
public boolean startAdvertising() { public boolean startAdvertising(final AdvertiseCallback callback) {
if (getState() != STATE_ON) return false; if (getState() != STATE_ON) return false;
try { try {
IBluetoothGatt iGatt = mManagerService.getBluetoothGatt(); IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
if (iGatt == null) { if (iGatt == null) {
@@ -516,18 +534,31 @@ public final class BluetoothAdapter {
// Restart/reset advertising packets if advertising is in progress. // Restart/reset advertising packets if advertising is in progress.
if (isAdvertising()) { if (isAdvertising()) {
// Invalid advertising callback. // Invalid advertising callback.
if (mAdvertisingCallback == null || mAdvertisingCallback.mLeHandle == -1) { if (mAdvertisingGattCallback == null || mAdvertisingGattCallback.mLeHandle == -1) {
Log.e(TAG, "failed to restart advertising, invalid callback"); Log.e(TAG, "failed to restart advertising, invalid callback");
return false; return false;
} }
iGatt.startAdvertising(mAdvertisingCallback.mLeHandle); iGatt.startAdvertising(mAdvertisingGattCallback.mLeHandle);
// Run the callback from main thread.
mHandler.post(new Runnable() {
@Override
public void run() {
// callback with status success.
callback.onAdvertiseStart(ADVERTISE_CALLBACK_SUCCESS);
}
});
return true; return true;
} }
UUID uuid = UUID.randomUUID(); UUID uuid = UUID.randomUUID();
GattCallbackWrapper wrapper = GattCallbackWrapper wrapper =
new GattCallbackWrapper(this, null, null, GattCallbackWrapper.CALLBACK_TYPE_ADV); new GattCallbackWrapper(this, null, null, callback);
iGatt.registerClient(new ParcelUuid(uuid), wrapper); iGatt.registerClient(new ParcelUuid(uuid), wrapper);
mAdvertisingCallback = wrapper; if (!wrapper.advertiseStarted()) {
return false;
}
synchronized (mLock) {
mAdvertisingGattCallback = wrapper;
}
return true; return true;
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "", e); Log.e(TAG, "", e);
@@ -537,25 +568,29 @@ public final class BluetoothAdapter {
/** /**
* Stop BLE advertising. * Stop BLE advertising.
* An app should stop advertising by requesting *
* {@link BluetoothAdapter#ACTION_STOP_ADVERTISING} instead of calling this method directly. * @param callback - {@link AdvertiseCallback}
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}
* @return true if BLE advertising stops, false otherwise. * @return true if BLE advertising stops, false otherwise.
* @hide * @hide
*/ */
public boolean stopAdvertisting() { public boolean stopAdvertising(AdvertiseCallback callback) {
try { try {
IBluetoothGatt iGatt = mManagerService.getBluetoothGatt(); IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
if (iGatt == null) { if (iGatt == null) {
// BLE is not supported // BLE is not supported
return false; return false;
} }
if (mAdvertisingCallback == null) { if (mAdvertisingGattCallback == null) {
// no callback. // no callback.
return false; return false;
} }
mAdvertisingCallback.stopAdvertising(); // Make sure same callback is used for start and stop advertising.
mAdvertisingCallback = null; if (callback != mAdvertisingGattCallback.mAdvertiseCallback) {
Log.e(TAG, "must use the same callback for star/stop advertising");
return false;
}
mAdvertisingGattCallback.stopAdvertising();
mAdvertisingGattCallback = null;
return true; return true;
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "", e); Log.e(TAG, "", e);
@@ -1415,6 +1450,8 @@ public final class BluetoothAdapter {
if (VDBG) Log.d(TAG, "onBluetoothServiceDown: " + mService); if (VDBG) Log.d(TAG, "onBluetoothServiceDown: " + mService);
synchronized (mManagerCallback) { synchronized (mManagerCallback) {
mService = null; mService = null;
// Reset bluetooth adv scan data when Gatt service is down.
mBluetoothAdvScanData = null;
for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks ){ for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks ){
try { try {
if (cb != null) { if (cb != null) {
@@ -1689,11 +1726,9 @@ public final class BluetoothAdapter {
private static class GattCallbackWrapper extends IBluetoothGattCallback.Stub { private static class GattCallbackWrapper extends IBluetoothGattCallback.Stub {
private static final int LE_CALLBACK_REG_TIMEOUT = 2000; private static final int LE_CALLBACK_REG_TIMEOUT = 2000;
private static final int LE_CALLBACK_REG_WAIT_COUNT = 5; private static final int LE_CALLBACK_REG_WAIT_COUNT = 5;
private static final int CALLBACK_TYPE_SCAN = 0;
private static final int CALLBACK_TYPE_ADV = 1;
private final AdvertiseCallback mAdvertiseCallback;
private final LeScanCallback mLeScanCb; private final LeScanCallback mLeScanCb;
private int mCallbackType;
// mLeHandle 0: not registered // mLeHandle 0: not registered
// -1: scan stopped // -1: scan stopped
@@ -1708,26 +1743,34 @@ public final class BluetoothAdapter {
mLeScanCb = leScanCb; mLeScanCb = leScanCb;
mScanFilter = uuid; mScanFilter = uuid;
mLeHandle = 0; mLeHandle = 0;
mCallbackType = CALLBACK_TYPE_SCAN; mAdvertiseCallback = null;
} }
public GattCallbackWrapper(BluetoothAdapter bluetoothAdapter, LeScanCallback leScanCb, public GattCallbackWrapper(BluetoothAdapter bluetoothAdapter, LeScanCallback leScanCb,
UUID[] uuid, int type) { UUID[] uuid, AdvertiseCallback callback) {
mBluetoothAdapter = new WeakReference<BluetoothAdapter>(bluetoothAdapter); mBluetoothAdapter = new WeakReference<BluetoothAdapter>(bluetoothAdapter);
mLeScanCb = leScanCb; mLeScanCb = leScanCb;
mScanFilter = uuid; mScanFilter = uuid;
mLeHandle = 0; mLeHandle = 0;
mCallbackType = type; mAdvertiseCallback = callback;
} }
public boolean scanStarted() { public boolean scanStarted() {
return waitForRegisteration(LE_CALLBACK_REG_WAIT_COUNT);
}
public boolean advertiseStarted() {
// Wait for registeration callback.
return waitForRegisteration(1);
}
private boolean waitForRegisteration(int maxWaitCount) {
boolean started = false; boolean started = false;
synchronized(this) { synchronized(this) {
if (mLeHandle == -1) return false; if (mLeHandle == -1) return false;
int count = 0; int count = 0;
// wait for callback registration and LE scan to start // wait for callback registration and LE scan to start
while (mLeHandle == 0 && count < LE_CALLBACK_REG_WAIT_COUNT) { while (mLeHandle == 0 && count < maxWaitCount) {
try { try {
wait(LE_CALLBACK_REG_TIMEOUT); wait(LE_CALLBACK_REG_TIMEOUT);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@@ -1751,7 +1794,7 @@ public final class BluetoothAdapter {
try { try {
IBluetoothGatt iGatt = adapter.getBluetoothManager().getBluetoothGatt(); IBluetoothGatt iGatt = adapter.getBluetoothManager().getBluetoothGatt();
iGatt.stopAdvertising(); iGatt.stopAdvertising();
Log.d(TAG, "unregeistering client " + mLeHandle); Log.d(TAG, "unregistering client " + mLeHandle);
iGatt.unregisterClient(mLeHandle); iGatt.unregisterClient(mLeHandle);
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Failed to stop advertising and unregister" + e); Log.e(TAG, "Failed to stop advertising and unregister" + e);
@@ -1805,7 +1848,7 @@ public final class BluetoothAdapter {
BluetoothAdapter adapter = mBluetoothAdapter.get(); BluetoothAdapter adapter = mBluetoothAdapter.get();
if (adapter != null) { if (adapter != null) {
iGatt = adapter.getBluetoothManager().getBluetoothGatt(); iGatt = adapter.getBluetoothManager().getBluetoothGatt();
if (mCallbackType == CALLBACK_TYPE_ADV) { if (mAdvertiseCallback != null) {
iGatt.startAdvertising(mLeHandle); iGatt.startAdvertising(mLeHandle);
} else { } else {
if (mScanFilter == null) { if (mScanFilter == null) {
@@ -1855,7 +1898,7 @@ public final class BluetoothAdapter {
* @hide * @hide
*/ */
public void onScanResult(String address, int rssi, byte[] advData) { public void onScanResult(String address, int rssi, byte[] advData) {
if (DBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi); if (VDBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
// Check null in case the scan has been stopped // Check null in case the scan has been stopped
synchronized(this) { synchronized(this) {
@@ -1944,9 +1987,13 @@ public final class BluetoothAdapter {
// no op // no op
} }
public void onListen(int status) { public void onAdvertiseStateChange(int advertiseState, int status) {
// no op Log.d(TAG, "on advertise call back, state: " + advertiseState + " status: " + status);
if (advertiseState == STATE_ADVERTISE_STARTED) {
mAdvertiseCallback.onAdvertiseStart(status);
} else {
mAdvertiseCallback.onAdvertiseStop(status);
}
} }
} }
} }

View File

@@ -77,6 +77,7 @@ public final class BluetoothAdvScanData {
try { try {
return mBluetoothGatt.setAdvManufacturerCodeAndData(manufacturerCode, manufacturerData); return mBluetoothGatt.setAdvManufacturerCodeAndData(manufacturerCode, manufacturerData);
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Unable to set manufacturer id and data.", e);
return false; return false;
} }
} }
@@ -92,6 +93,7 @@ public final class BluetoothAdvScanData {
try { try {
return mBluetoothGatt.setAdvServiceData(serviceData); return mBluetoothGatt.setAdvServiceData(serviceData);
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Unable to set service data.", e);
return false; return false;
} }
} }
@@ -103,6 +105,7 @@ public final class BluetoothAdvScanData {
try { try {
return Collections.unmodifiableList(mBluetoothGatt.getAdvServiceUuids()); return Collections.unmodifiableList(mBluetoothGatt.getAdvServiceUuids());
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Unable to get service uuids.", e);
return null; return null;
} }
} }
@@ -115,6 +118,7 @@ public final class BluetoothAdvScanData {
try { try {
return mBluetoothGatt.getAdvManufacturerData(); return mBluetoothGatt.getAdvManufacturerData();
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Unable to get manufacturer data.", e);
return null; return null;
} }
} }
@@ -127,6 +131,7 @@ public final class BluetoothAdvScanData {
try { try {
return mBluetoothGatt.getAdvServiceData(); return mBluetoothGatt.getAdvServiceData();
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Unable to get service data.", e);
return null; return null;
} }
} }
@@ -140,7 +145,7 @@ public final class BluetoothAdvScanData {
try { try {
mBluetoothGatt.removeAdvManufacturerCodeAndData(manufacturerCode); mBluetoothGatt.removeAdvManufacturerCodeAndData(manufacturerCode);
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, e.toString()); Log.e(TAG, "Unable to remove manufacturer : " + manufacturerCode, e);
} }
} }
} }

View File

@@ -16,8 +16,6 @@
package android.bluetooth; package android.bluetooth;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context; import android.content.Context;
import android.os.ParcelUuid; import android.os.ParcelUuid;
import android.os.RemoteException; import android.os.RemoteException;
@@ -544,6 +542,15 @@ public final class BluetoothGatt implements BluetoothProfile {
Log.w(TAG, "Unhandled exception in callback", ex); Log.w(TAG, "Unhandled exception in callback", ex);
} }
} }
/**
* Advertise state change callback
* @hide
*/
public void onAdvertiseStateChange(int state, int status) {
if (DBG) Log.d(TAG, "onAdvertiseStateChange() - state = "
+ state + " status=" + status);
}
}; };
/*package*/ BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device) { /*package*/ BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device) {

View File

@@ -63,4 +63,5 @@ interface IBluetoothGattCallback {
in int charInstId, in ParcelUuid charUuid, in int charInstId, in ParcelUuid charUuid,
in byte[] value); in byte[] value);
void onReadRemoteRssi(in String address, in int rssi, in int status); void onReadRemoteRssi(in String address, in int rssi, in int status);
oneway void onAdvertiseStateChange(in int advertiseState, in int status);
} }