Bluetooth LE background operation mode (2/2)
Changes include new framework APIs to enable and disable Bluetooth LE
separately from Bluetooth Classic. Along with handling the new states
in the Bluetooth manager service.
Change-Id: Idf667981f48fcbcb6dfda1aa77ea8bab1b2361f0
diff --git a/framework/java/android/bluetooth/BluetoothAdapter.java b/framework/java/android/bluetooth/BluetoothAdapter.java
index ac74a62..88bb626 100644
--- a/framework/java/android/bluetooth/BluetoothAdapter.java
+++ b/framework/java/android/bluetooth/BluetoothAdapter.java
@@ -34,6 +34,9 @@
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.app.ActivityThread;
+import android.os.SystemProperties;
+import android.os.Binder;
import android.util.Log;
import android.util.Pair;
@@ -121,6 +124,9 @@
* {@link #STATE_TURNING_ON},
* {@link #STATE_ON},
* {@link #STATE_TURNING_OFF},
+ * {@link #STATE_BLE_TURNING_ON},
+ * {@link #STATE_BLE_ON},
+ * {@link #STATE_BLE_TURNING_OFF},
*/
public static final String EXTRA_STATE =
"android.bluetooth.adapter.extra.STATE";
@@ -131,6 +137,9 @@
* {@link #STATE_TURNING_ON},
* {@link #STATE_ON},
* {@link #STATE_TURNING_OFF},
+ * {@link #STATE_BLE_TURNING_ON},
+ * {@link #STATE_BLE_ON},
+ * {@link #STATE_BLE_TURNING_OFF},
*/
public static final String EXTRA_PREVIOUS_STATE =
"android.bluetooth.adapter.extra.PREVIOUS_STATE";
@@ -156,6 +165,24 @@
public static final int STATE_TURNING_OFF = 13;
/**
+ * Indicates the local Bluetooth adapter is turning Bluetooth LE mode on.
+ * @hide
+ */
+ public static final int STATE_BLE_TURNING_ON = 14;
+
+ /**
+ * Indicates the local Bluetooth adapter is in LE only mode.
+ * @hide
+ */
+ public static final int STATE_BLE_ON = 15;
+
+ /**
+ * Indicates the local Bluetooth adapter is turning off LE only mode.
+ * @hide
+ */
+ public static final int STATE_BLE_TURNING_OFF = 16;
+
+ /**
* Activity Action: Show a system activity that requests discoverable mode.
* This activity will also request the user to turn on Bluetooth if it
* is not currently enabled.
@@ -366,6 +393,39 @@
public static final String EXTRA_PREVIOUS_CONNECTION_STATE =
"android.bluetooth.adapter.extra.PREVIOUS_CONNECTION_STATE";
+ /**
+ * Broadcast Action: The Bluetooth adapter state has changed in LE only mode.
+ * @hide
+ */
+ public static final String ACTION_BLE_STATE_CHANGED =
+ "anrdoid.bluetooth.adapter.action.BLE_STATE_CHANGED";
+
+ /**
+ * Broadcast Action: The notifys Bluetooth ACL connected event. This will be
+ * by BLE Always on enabled application to know the ACL_CONNECTED event
+ * when Bluetooth state in STATE_BLE_ON. This denotes GATT connection
+ * as Bluetooth LE is the only feature available in STATE_BLE_ON
+ *
+ * This is counterpart of {@link BluetoothDevice#ACTION_ACL_CONNECTED} which
+ * works in Bluetooth state STATE_ON
+ * @hide
+ */
+ public static final String ACTION_BLE_ACL_CONNECTED =
+ "android.bluetooth.adapter.action.BLE_ACL_CONNECTED";
+
+ /**
+ * Broadcast Action: The notifys Bluetooth ACL connected event. This will be
+ * by BLE Always on enabled application to know the ACL_DISCONNECTED event
+ * when Bluetooth state in STATE_BLE_ON. This denotes GATT disconnection as Bluetooth
+ * LE is the only feature available in STATE_BLE_ON
+ *
+ * This is counterpart of {@link BluetoothDevice#ACTION_ACL_DISCONNECTED} which
+ * works in Bluetooth state STATE_ON
+ * @hide
+ */
+ public static final String ACTION_BLE_ACL_DISCONNECTED =
+ "android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED";
+
/** The profile is in disconnected state */
public static final int STATE_DISCONNECTED = 0;
/** The profile is in connecting state */
@@ -377,6 +437,7 @@
/** @hide */
public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
+ private final IBinder mToken;
/** When creating a ServerSocket using listenUsingRfcommOn() or
@@ -447,6 +508,7 @@
} catch (RemoteException e) {Log.e(TAG, "", e);}
mManagerService = managerService;
mLeScanClients = new HashMap<LeScanCallback, ScanCallback>();
+ mToken = new Binder();
}
/**
@@ -493,11 +555,9 @@
* on this device before calling this method.
*/
public BluetoothLeAdvertiser getBluetoothLeAdvertiser() {
- if (getState() != STATE_ON) {
- return null;
- }
+ if (!getLeAccess()) return null;
if (!isMultipleAdvertisementSupported() && !isPeripheralModeSupported()) {
- Log.e(TAG, "bluetooth le advertising not supported");
+ Log.e(TAG, "Bluetooth LE advertising not supported");
return null;
}
synchronized(mLock) {
@@ -512,9 +572,7 @@
* Returns a {@link BluetoothLeScanner} object for Bluetooth LE scan operations.
*/
public BluetoothLeScanner getBluetoothLeScanner() {
- if (getState() != STATE_ON) {
- return null;
- }
+ if (!getLeAccess()) return null;
synchronized(mLock) {
if (sBluetoothLeScanner == null) {
sBluetoothLeScanner = new BluetoothLeScanner(mManagerService);
@@ -532,7 +590,6 @@
* @return true if the local adapter is turned on
*/
public boolean isEnabled() {
-
try {
synchronized(mManagerCallback) {
if (mService != null) return mService.isEnabled();
@@ -542,6 +599,178 @@
}
/**
+ * Return true if Bluetooth LE(Always BLE On feature) is currently
+ * enabled and ready for use
+ * <p>This returns true if current state is either STATE_ON or STATE_BLE_ON
+ *
+ * @return true if the local Bluetooth LE adapter is turned on
+ * @hide
+ */
+ public boolean isLeEnabled() {
+ final int state = getLeState();
+ if (state == BluetoothAdapter.STATE_ON) {
+ if (DBG) Log.d (TAG, "STATE_ON");
+ } else if (state == BluetoothAdapter.STATE_BLE_ON) {
+ if (DBG) Log.d (TAG, "STATE_BLE_ON");
+ } else {
+ if (DBG) Log.d (TAG, "STATE_OFF");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Performs action based on user action to turn BT ON
+ * or OFF if BT is in BLE_ON state
+ */
+ private void notifyUserAction(boolean enable) {
+ if (mService == null) {
+ Log.e(TAG, "mService is null");
+ return;
+ }
+
+ try {
+ if (enable) {
+ mService.onLeServiceUp(); //NA:TODO implementation pending
+ } else {
+ mService.onBrEdrDown(); //NA:TODO implementation pending
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ }
+
+ /**
+ * Returns true if LE only mode is enabled, that is apps
+ * have authorization to turn only BT ON and the calling
+ * app has privilage to do so
+ */
+ private boolean isLEAlwaysOnEnabled() {
+ boolean ret = false;
+ if (SystemProperties.getBoolean("ro.bluetooth.blealwayson", true) == true) {
+ Log.v(TAG, "LE always on mode is enabled");
+ // TODO: System API authorization check
+ ret = true;
+ } else {
+ Log.v(TAG, "LE always on mode is disabled");
+ ret = false;
+ }
+ return ret;
+ }
+
+ /**
+ * Turns off Bluetooth LE which was earlier turned on by calling EnableBLE().
+ *
+ * <p> If the internal Adapter state is STATE_BLE_ON, this would trigger the transition
+ * to STATE_OFF and completely shut-down Bluetooth
+ *
+ * <p> If the Adapter state is STATE_ON, This would unregister the existance of
+ * special Bluetooth LE application and hence the further turning off of Bluetooth
+ * from UI would ensure the complete turn-off of Bluetooth rather than staying back
+ * BLE only state
+ *
+ * <p>This is an asynchronous call: it will return immediately, and
+ * clients should listen for {@link #ACTION_BLE_STATE_CHANGED}
+ * to be notified of subsequent adapter state changes If this call returns
+ * true, then the adapter state will immediately transition from {@link
+ * #STATE_ON} to {@link #STATE_TURNING_OFF}, and some time
+ * later transition to either {@link #STATE_BLE_ON} or {@link
+ * #STATE_OFF} based on the existance of the further Always BLE ON enabled applications
+ * If this call returns false then there was an
+ * immediate problem that will prevent the QAdapter from being turned off -
+ * such as the QAadapter already being turned off.
+ *
+ * @return true to indicate success, or false on
+ * immediate error
+ * @hide
+ */
+ public boolean disableBLE() {
+ if (isLEAlwaysOnEnabled() != true) return false;
+
+ int state = getLeState();
+ if (state == BluetoothAdapter.STATE_ON) {
+ if (DBG) Log.d (TAG, "STATE_ON: shouldn't disable");
+ try {
+ mManagerService.updateBleAppCount(mToken, false);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ return true;
+
+ } else if (state == BluetoothAdapter.STATE_BLE_ON) {
+ if (DBG) Log.d (TAG, "STATE_BLE_ON");
+ int bleAppCnt = 0;
+ try {
+ bleAppCnt = mManagerService.updateBleAppCount(mToken, false);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ if (bleAppCnt == 0) {
+ // Disable only if there are no other clients
+ notifyUserAction(false);
+ }
+ return true;
+ }
+
+ if (DBG) Log.d (TAG, "STATE_OFF: Already disabled");
+ return false;
+ }
+
+ /**
+ * Special Applications who want to only turn on Bluetooth Low Energy (BLE) would
+ * EnableBLE, EnableBLE brings-up Bluetooth so that application can access
+ * only LE related feature (Bluetooth GATT layers interfaces using the respective class)
+ * EnableBLE in turn registers the existance of a special App which wants to
+ * turn on Bluetooth Low enrgy part without making it visible at the settings UI
+ * as Bluetooth ON.
+ * <p>Invoking EnableBLE when Bluetooth is already in ON state, would just registers
+ * the existance of special Application and doesn't do anything to current BT state.
+ * when user turn OFF Bluetooth from UI, if there is an existance of special app, Bluetooth
+ * would stay in BLE_ON state so that LE features are still acessible to the special
+ * Applications.
+ *
+ * <p>This is an asynchronous call: it will return immediately, and
+ * clients should listen for {@link #ACTION_BLE_STATE_CHANGED}
+ * to be notified of subsequent adapter state changes. If this call returns
+ * true, then the adapter state will immediately transition from {@link
+ * #STATE_OFF} to {@link #STATE_BLE_TURNING_ON}, and some time
+ * later transition to either {@link #STATE_OFF} or {@link
+ * #STATE_BLE_ON}. If this call returns false then there was an
+ * immediate problem that will prevent the adapter from being turned on -
+ * such as Airplane mode, or the adapter is already turned on.
+ * (@link #ACTION_BLE_STATE_CHANGED) returns the Bluetooth Adapter's various
+ * states, It includes all the classic Bluetooth Adapter states along with
+ * internal BLE only states
+ *
+ * @return true to indicate Bluetooth LE start-up has begun, or false on
+ * immediate error
+ * @hide
+ */
+ public boolean enableBLE() {
+ if (isLEAlwaysOnEnabled() != true) return false;
+
+ if (isLeEnabled() == true) {
+ if (DBG) Log.d(TAG, "enableBLE(): BT is already enabled..!");
+ try {
+ mManagerService.updateBleAppCount(mToken, true);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ return true;
+ }
+
+ try {
+ if (DBG) Log.d(TAG, "Calling enableBLE");
+ mManagerService.updateBleAppCount(mToken, true);
+ return mManagerService.enable();
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+
+ return false;
+ }
+
+ /**
* Get the current state of the local Bluetooth adapter.
* <p>Possible return values are
* {@link #STATE_OFF},
@@ -559,6 +788,13 @@
{
int state= mService.getState();
if (VDBG) Log.d(TAG, "" + hashCode() + ": getState(). Returning " + state);
+ //consider all internal states as OFF
+ if (state == BluetoothAdapter.STATE_BLE_ON
+ || state == BluetoothAdapter.STATE_BLE_TURNING_ON
+ || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) {
+ if (VDBG) Log.d(TAG, "Consider internal state as OFF");
+ state = BluetoothAdapter.STATE_OFF;
+ }
return state;
}
// TODO(BT) there might be a small gap during STATE_TURNING_ON that
@@ -570,6 +806,49 @@
}
/**
+ * Get the current state of the local Bluetooth adapter
+ * <p>This returns current internal state of Adapter including LE ON/OFF
+ *
+ * <p>Possible return values are
+ * {@link #STATE_OFF},
+ * {@link #STATE_BLE_TURNING_ON},
+ * {@link #STATE_BLE_ON},
+ * {@link #STATE_TURNING_ON},
+ * {@link #STATE_ON},
+ * {@link #STATE_TURNING_OFF},
+ * {@link #STATE_BLE_TURNING_OFF}.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+ *
+ * @return current state of Bluetooth adapter
+ * @hide
+ */
+ public int getLeState() {
+ try {
+ synchronized(mManagerCallback) {
+ if (mService != null)
+ {
+ int state= mService.getState();
+ if (VDBG) Log.d(TAG,"getLeState() returning " + state);
+ return state;
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ }
+ return BluetoothAdapter.STATE_OFF;
+ }
+
+ boolean getLeAccess() {
+ if(getLeState() == STATE_ON)
+ return true;
+
+ else if (getLeState() == STATE_BLE_ON)
+ return true; // TODO: FILTER SYSTEM APPS HERE <--
+
+ return false;
+ }
+
+ /**
* Turn on the local Bluetooth adapter—do not use without explicit
* user action to turn on Bluetooth.
* <p>This powers on the underlying Bluetooth hardware, and starts all
@@ -597,10 +876,23 @@
* immediate error
*/
public boolean enable() {
+ int state = STATE_OFF;
if (isEnabled() == true){
if (DBG) Log.d(TAG, "enable(): BT is already enabled..!");
return true;
}
+ //Use service interface to get the exact state
+ if (mService != null) {
+ try {
+ state = mService.getState();
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ }
+
+ if (state == BluetoothAdapter.STATE_BLE_ON) {
+ Log.e(TAG, "BT is in BLE_ON State");
+ notifyUserAction(true);
+ return true;
+ }
try {
return mManagerService.enable();
} catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -1561,6 +1853,10 @@
}
}
}
+
+ public void onBrEdrDown() {
+ if (VDBG) Log.i(TAG, "on QBrEdrDown: ");
+ }
};
/**
diff --git a/framework/java/android/bluetooth/BluetoothDevice.java b/framework/java/android/bluetooth/BluetoothDevice.java
index 1fdf9f4..1e82c03 100644
--- a/framework/java/android/bluetooth/BluetoothDevice.java
+++ b/framework/java/android/bluetooth/BluetoothDevice.java
@@ -606,7 +606,9 @@
public void onBluetoothServiceUp(IBluetooth bluetoothService)
throws RemoteException {
synchronized (BluetoothDevice.class) {
- sService = bluetoothService;
+ if (sService == null) {
+ sService = bluetoothService;
+ }
}
}
@@ -616,6 +618,11 @@
sService = null;
}
}
+
+ public void onBrEdrDown()
+ {
+ if (DBG) Log.d(TAG, "onBrEdrDown: reached BLE ON state");
+ }
};
/**
* Create a new BluetoothDevice
@@ -1030,7 +1037,7 @@
* or null on error
*/
public ParcelUuid[] getUuids() {
- if (sService == null) {
+ if (sService == null || isBluetoothEnabled() == false) {
Log.e(TAG, "BT not enabled. Cannot get remote device Uuids");
return null;
}
@@ -1057,7 +1064,7 @@
*/
public boolean fetchUuidsWithSdp() {
IBluetooth service = sService;
- if (service == null) {
+ if (service == null || isBluetoothEnabled() == false) {
Log.e(TAG, "BT not enabled. Cannot fetchUuidsWithSdp");
return false;
}
@@ -1099,16 +1106,6 @@
return false;
}
- /** @hide */
- public int getServiceChannel(ParcelUuid uuid) {
- //TODO(BT)
- /*
- try {
- return sService.getRemoteServiceChannel(this, uuid);
- } catch (RemoteException e) {Log.e(TAG, "", e);}*/
- return BluetoothDevice.ERROR;
- }
-
/**
* Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN}
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
@@ -1187,6 +1184,15 @@
return false;
}
+ boolean isBluetoothEnabled() {
+ boolean ret = false;
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null && adapter.isEnabled() == true) {
+ ret = true;
+ }
+ return ret;
+ }
+
/**
* Requires {@link android.Manifest.permission#BLUETOOTH}.
* @return Whether the phonebook access is allowed to this device. Can be
@@ -1289,6 +1295,10 @@
* @hide
*/
public BluetoothSocket createRfcommSocket(int channel) throws IOException {
+ if (isBluetoothEnabled() == false) {
+ Log.e(TAG, "Bluetooth is not enabled");
+ throw new IOException();
+ }
return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, channel,
null);
}
@@ -1355,6 +1365,11 @@
* insufficient permissions
*/
public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException {
+ if (isBluetoothEnabled() == false) {
+ Log.e(TAG, "Bluetooth is not enabled");
+ throw new IOException();
+ }
+
return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, -1,
new ParcelUuid(uuid));
}
@@ -1388,6 +1403,10 @@
* insufficient permissions
*/
public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) throws IOException {
+ if (isBluetoothEnabled() == false) {
+ Log.e(TAG, "Bluetooth is not enabled");
+ throw new IOException();
+ }
return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, -1,
new ParcelUuid(uuid));
}
@@ -1407,6 +1426,11 @@
* @hide
*/
public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException {
+
+ if (isBluetoothEnabled() == false) {
+ Log.e(TAG, "Bluetooth is not enabled");
+ throw new IOException();
+ }
return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, port,
null);
}
@@ -1422,6 +1446,11 @@
* @hide
*/
public BluetoothSocket createScoSocket() throws IOException {
+
+ if (isBluetoothEnabled() == false) {
+ Log.e(TAG, "Bluetooth is not enabled");
+ throw new IOException();
+ }
return new BluetoothSocket(BluetoothSocket.TYPE_SCO, -1, true, true, this, -1, null);
}
diff --git a/framework/java/android/bluetooth/IBluetooth.aidl b/framework/java/android/bluetooth/IBluetooth.aidl
index af560df..90df198 100644
--- a/framework/java/android/bluetooth/IBluetooth.aidl
+++ b/framework/java/android/bluetooth/IBluetooth.aidl
@@ -102,4 +102,6 @@
// for dumpsys support
String dump();
+ void onLeServiceUp();
+ void onBrEdrDown();
}
diff --git a/framework/java/android/bluetooth/IBluetoothGatt.aidl b/framework/java/android/bluetooth/IBluetoothGatt.aidl
index 7070bae..4ca57f8 100644
--- a/framework/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/framework/java/android/bluetooth/IBluetoothGatt.aidl
@@ -101,4 +101,6 @@
in int srvcInstanceId, in ParcelUuid srvcId,
in int charInstanceId, in ParcelUuid charId,
in boolean confirm, in byte[] value);
+ void disconnectAll();
+ void unregAll();
}
diff --git a/framework/java/android/bluetooth/IBluetoothManager.aidl b/framework/java/android/bluetooth/IBluetoothManager.aidl
index 7411d3f..8d1ce99 100644
--- a/framework/java/android/bluetooth/IBluetoothManager.aidl
+++ b/framework/java/android/bluetooth/IBluetoothManager.aidl
@@ -44,4 +44,6 @@
String getAddress();
String getName();
+ int updateBleAppCount(IBinder b, boolean enable);
+ boolean isBleAppPresent();
}
diff --git a/framework/java/android/bluetooth/IBluetoothManagerCallback.aidl b/framework/java/android/bluetooth/IBluetoothManagerCallback.aidl
index 9551086..1385daf 100644
--- a/framework/java/android/bluetooth/IBluetoothManagerCallback.aidl
+++ b/framework/java/android/bluetooth/IBluetoothManagerCallback.aidl
@@ -26,4 +26,5 @@
interface IBluetoothManagerCallback {
void onBluetoothServiceUp(in IBluetooth bluetoothService);
void onBluetoothServiceDown();
-}
\ No newline at end of file
+ void onBrEdrDown();
+}
diff --git a/framework/java/android/bluetooth/le/BluetoothLeUtils.java b/framework/java/android/bluetooth/le/BluetoothLeUtils.java
index 4916bd9..c40256b 100644
--- a/framework/java/android/bluetooth/le/BluetoothLeUtils.java
+++ b/framework/java/android/bluetooth/le/BluetoothLeUtils.java
@@ -132,7 +132,7 @@
* {@link BluetoothAdapter#STATE_ON}.
*/
static void checkAdapterStateOn(BluetoothAdapter adapter) {
- if (adapter == null || adapter.getState() != BluetoothAdapter.STATE_ON) {
+ if (adapter == null || !adapter.isLeEnabled()) {//adapter.getState() != BluetoothAdapter.STATE_ON) {
throw new IllegalStateException("BT Adapter is not turned ON");
}
}
diff --git a/service/java/com/android/server/bluetooth/BluetoothManagerService.java b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
index 32a6a2f..46a4599 100644
--- a/service/java/com/android/server/bluetooth/BluetoothManagerService.java
+++ b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
@@ -58,6 +58,7 @@
import java.util.List;
import java.util.Vector;
+import java.util.*;
class BluetoothManagerService extends IBluetoothManager.Stub {
private static final String TAG = "BluetoothManagerService";
private static final boolean DBG = true;
@@ -114,6 +115,7 @@
private static final int SERVICE_IBLUETOOTHGATT = 2;
private final Context mContext;
+ private static int mBleAppCount = 0;
// Locks are not provided for mName and mAddress.
// They are accessed in handler or broadcast receiver, same thread context.
@@ -186,11 +188,40 @@
persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
}
}
+
+ int st = BluetoothAdapter.STATE_OFF;
+ if (mBluetooth != null) {
+ try {
+ st = mBluetooth.getState();
+ } catch (RemoteException e) {
+ Log.e(TAG,"Unable to call getState", e);
+ }
+ }
+ Log.d(TAG, "state" + st);
+
if (isAirplaneModeOn()) {
- // disable without persisting the setting
- sendDisableMsg();
+ // Clear registered LE apps to force shut-off
+ synchronized (this) {
+ mBleAppCount = 0;
+ }
+ if (st == BluetoothAdapter.STATE_BLE_ON) {
+ //if state is BLE_ON make sure you trigger disableBLE part
+ try {
+ if (mBluetooth != null) {
+ mBluetooth.onBrEdrDown();
+ mEnableExternal = false;
+ }
+ } catch(RemoteException e) {
+ Log.e(TAG,"Unable to call onBrEdrDown", e);
+ }
+ } else if (st == BluetoothAdapter.STATE_ON){
+ // disable without persisting the setting
+ Log.d(TAG, "Calling disable");
+ sendDisableMsg();
+ }
} else if (mEnableExternal) {
// enable without persisting the setting
+ Log.d(TAG, "Calling enable");
sendEnableMsg(mQuietEnableExternal);
}
}
@@ -205,12 +236,6 @@
sendEnableMsg(mQuietEnableExternal);
}
}
-
- if (!isNameAndAddressSet()) {
- //Sync the Bluetooth name and address from the Bluetooth Adapter
- if (DBG) Log.d(TAG,"Retrieving Bluetooth Adapter name and address...");
- getNameAndAddress();
- }
}
}
};
@@ -220,6 +245,7 @@
mContext = context;
mBluetooth = null;
+ mBluetoothGatt = null;
mBinding = false;
mUnbinding = false;
mEnable = false;
@@ -398,6 +424,133 @@
return false;
}
+ class ClientDeathRecipient implements IBinder.DeathRecipient {
+ public void binderDied() {
+ if (DBG) Log.d(TAG, "Binder is dead - unregister Ble App");
+ if (mBleAppCount > 0) --mBleAppCount;
+
+ if (mBleAppCount == 0) {
+ if (DBG) Log.d(TAG, "Disabling LE only mode after application crash");
+ try {
+ if (mBluetooth != null) {
+ mBluetooth.onBrEdrDown();
+ }
+ } catch(RemoteException e) {
+ Log.e(TAG,"Unable to call onBrEdrDown", e);
+ }
+ }
+ }
+ }
+
+ /** Internal death rec list */
+ Map<IBinder, ClientDeathRecipient> mBleApps = new HashMap<IBinder, ClientDeathRecipient>();
+
+ public int updateBleAppCount(IBinder token, boolean enable) {
+ if (enable) {
+ ClientDeathRecipient r = mBleApps.get(token);
+ if (r == null) {
+ ClientDeathRecipient deathRec = new ClientDeathRecipient();
+ try {
+ token.linkToDeath(deathRec, 0);
+ } catch (RemoteException ex) {
+ throw new IllegalArgumentException("Wake lock is already dead.");
+ }
+ mBleApps.put(token, deathRec);
+ synchronized (this) {
+ ++mBleAppCount;
+ }
+ if (DBG) Log.d(TAG, "Registered for death Notification");
+ }
+
+ } else {
+ ClientDeathRecipient r = mBleApps.get(token);
+ if (r != null) {
+ try {
+ token.linkToDeath(r, 0);
+ } catch (RemoteException ex) {
+ throw new IllegalArgumentException("Wake lock is already dead.");
+ }
+ mBleApps.remove(token);
+ synchronized (this) {
+ if (mBleAppCount > 0) --mBleAppCount;
+ }
+ if (DBG) Log.d(TAG, "Unregistered for death Notification");
+ }
+ }
+ if (DBG) Log.d(TAG, "Updated BleAppCount" + mBleAppCount);
+ if (mBleAppCount == 0 && mEnable) {
+ try {
+ if (mBluetooth != null && (mBluetooth.getState() != BluetoothAdapter.STATE_ON)) {
+ if (DBG) Log.d(TAG, "Reseting the mEnable flag for clean disable");
+ mEnable = false;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "getState()", e);
+ }
+ }
+ return mBleAppCount;
+ }
+
+ /** @hide*/
+ public boolean isBleAppPresent() {
+ if (DBG) Log.d(TAG, "isBleAppPresent() count: " + mBleAppCount);
+ return (mBleAppCount > 0);
+ }
+
+ /**
+ * Action taken when GattService is turned off
+ */
+ private void onBluetoothGattServiceUp() {
+ if (DBG) Log.d(TAG,"BluetoothGatt Service is Up");
+ try{
+ if (isBleAppPresent() == false && mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) {
+ mBluetooth.onLeServiceUp();
+
+ // waive WRITE_SECURE_SETTINGS permission check
+ long callingIdentity = Binder.clearCallingIdentity();
+ persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ } catch(RemoteException e) {
+ Log.e(TAG,"Unable to call onServiceUp", e);
+ }
+ }
+
+ /**
+ * Inform BluetoothAdapter instances that BREDR part is down
+ * and turn off all service and stack if no LE app needs it
+ */
+ private void sendBrEdrDownCallback() {
+ if (DBG) Log.d(TAG,"Calling sendBrEdrDownCallback callbacks");
+ int n = mCallbacks.beginBroadcast();
+
+ if (isBleAppPresent() == false) {
+ try {
+ mBluetooth.onBrEdrDown();
+ } catch(RemoteException e) {
+ Log.e(TAG,"Unable to call onBrEdrDown", e);
+ }
+ }
+ else{//need to stay at BLE ON. disconnect all Gatt connections
+ try{
+ mBluetoothGatt.unregAll();//disconnectAll();
+ } catch(RemoteException e) {
+ Log.e(TAG,"Unable to disconn all", e);
+ }
+ }
+
+ Log.d(TAG,"Broadcasting onBrEdrDown() to " + n + " receivers.");
+ for (int i=0; i <n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onBrEdrDown();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to call sendBrEdrDownCallback() on callback #" + i, e);
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+
+ /** @hide*/
public void getNameAndAddress() {
if (DBG) {
Log.d(TAG,"getNameAndAddress(): mBluetooth = " + mBluetooth +
@@ -447,11 +600,9 @@
mQuietEnableExternal = false;
mEnableExternal = true;
// waive WRITE_SECURE_SETTINGS permission check
- long callingIdentity = Binder.clearCallingIdentity();
- persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
- Binder.restoreCallingIdentity(callingIdentity);
sendEnableMsg(false);
}
+ if (DBG) Log.d(TAG, "enable returning");
return true;
}
@@ -510,6 +661,7 @@
} else {
mUnbinding=false;
}
+ mBluetoothGatt = null;
}
}
@@ -1036,6 +1188,7 @@
synchronized(mConnection) {
if (msg.arg1 == SERVICE_IBLUETOOTHGATT) {
mBluetoothGatt = IBluetoothGatt.Stub.asInterface(service);
+ onBluetoothGattServiceUp();
break;
} // else must be SERVICE_IBLUETOOTH
@@ -1113,12 +1266,18 @@
bluetoothStateChangeHandler(prevState, newState);
// handle error state transition case from TURNING_ON to OFF
// unbind and rebind bluetooth service and enable bluetooth
- if ((prevState == BluetoothAdapter.STATE_TURNING_ON) &&
+ if ((prevState == BluetoothAdapter.STATE_BLE_TURNING_ON) &&
(newState == BluetoothAdapter.STATE_OFF) &&
(mBluetooth != null) && mEnable) {
recoverBluetoothServiceFromError();
}
- if (newState == BluetoothAdapter.STATE_ON) {
+ if ((prevState == BluetoothAdapter.STATE_TURNING_ON) &&
+ (newState == BluetoothAdapter.STATE_BLE_ON) &&
+ (mBluetooth != null) && mEnable) {
+ recoverBluetoothServiceFromError();
+ }
+ if (newState == BluetoothAdapter.STATE_ON ||
+ newState == BluetoothAdapter.STATE_BLE_ON) {
// bluetooth is working, reset the counter
if (mErrorRecoveryRetryCounter != 0) {
Log.w(TAG, "bluetooth is recovered from error");
@@ -1378,39 +1537,90 @@
return valid;
}
+ private void sendBleStateChanged(int prevState, int newState) {
+ if (DBG) Log.d(TAG,"BLE State Change Intent: " + prevState + " -> " + newState);
+ // Send broadcast message to everyone else
+ Intent intent = new Intent(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
+ intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_PERM);
+ }
+
private void bluetoothStateChangeHandler(int prevState, int newState) {
+ boolean isStandardBroadcast = true;
if (prevState != newState) {
//Notify all proxy objects first of adapter state change
- if (newState == BluetoothAdapter.STATE_ON || newState == BluetoothAdapter.STATE_OFF) {
- boolean isUp = (newState==BluetoothAdapter.STATE_ON);
- sendBluetoothStateCallback(isUp);
+ if (newState == BluetoothAdapter.STATE_BLE_ON
+ || newState == BluetoothAdapter.STATE_OFF) {
+ boolean intermediate_off = (prevState == BluetoothAdapter.STATE_TURNING_OFF
+ && newState == BluetoothAdapter.STATE_BLE_ON);
- if (isUp) {
- // connect to GattService
- if (mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_BLUETOOTH_LE)) {
- Intent i = new Intent(IBluetoothGatt.class.getName());
- doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
- UserHandle.CURRENT);
- }
- } else {
- //If Bluetooth is off, send service down event to proxy objects, and unbind
- if (!isUp && canUnbindBluetoothService()) {
- unbindAllBluetoothProfileServices();
+ if (newState == BluetoothAdapter.STATE_OFF) {
+ // If Bluetooth is off, send service down event to proxy objects, and unbind
+ if (DBG) Log.d(TAG, "Bluetooth is complete turn off");
+ if (canUnbindBluetoothService()) {
+ if (DBG) Log.d(TAG, "Good to unbind!");
sendBluetoothServiceDownCallback();
unbindAndFinish();
+ sendBleStateChanged(prevState, newState);
+ // Don't broadcast as it has already been broadcast before
+ isStandardBroadcast = false;
}
+
+ } else if (!intermediate_off) {
+ // connect to GattService
+ if (DBG) Log.d(TAG, "Bluetooth is in LE only mode");
+ if (mBluetoothGatt != null) {
+ if (DBG) Log.d(TAG, "Calling BluetoothGattServiceUp");
+ onBluetoothGattServiceUp();
+ } else {
+ if (DBG) Log.d(TAG, "Binding Bluetooth GATT service");
+ if (mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_BLUETOOTH_LE)) {
+ Intent i = new Intent(IBluetoothGatt.class.getName());
+ doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.CURRENT);
+ }
+ }
+ sendBleStateChanged(prevState, newState);
+ //Don't broadcase this as std intent
+ isStandardBroadcast = false;
+
+ } else if (intermediate_off){
+ if (DBG) Log.d(TAG, "Intermediate off, back to LE only mode");
+ // For LE only mode, broadcast as is
+ sendBleStateChanged(prevState, newState);
+ sendBluetoothStateCallback(false); // BT is OFF for general users
+ // Broadcast as STATE_OFF
+ newState = BluetoothAdapter.STATE_OFF;
+ sendBrEdrDownCallback();
}
+ } else if (newState == BluetoothAdapter.STATE_ON) {
+ boolean isUp = (newState==BluetoothAdapter.STATE_ON);
+ sendBluetoothStateCallback(isUp);
+ sendBleStateChanged(prevState, newState);
+
+ } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON
+ || newState == BluetoothAdapter.STATE_BLE_TURNING_OFF ) {
+ sendBleStateChanged(prevState, newState);
+ isStandardBroadcast = false;
+
+ } else if (newState == BluetoothAdapter.STATE_TURNING_ON
+ || newState == BluetoothAdapter.STATE_TURNING_OFF) {
+ sendBleStateChanged(prevState, newState);
}
- //Send broadcast message to everyone else
- Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
- intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState);
- intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- if (DBG) Log.d(TAG,"Bluetooth State Change Intent: " + prevState + " -> " + newState);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
- BLUETOOTH_PERM);
+ if (isStandardBroadcast) {
+ if (prevState == BluetoothAdapter.STATE_BLE_ON) {
+ // Show prevState of BLE_ON as OFF to standard users
+ prevState = BluetoothAdapter.STATE_OFF;
+ }
+ Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
+ intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_PERM);
+ }
}
}