diff options
8 files changed, 601 insertions, 60 deletions
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 87d5bb0..dd030f8 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -32,6 +32,9 @@ import android.os.IBinder; 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; @@ -118,6 +121,9 @@ public final class BluetoothAdapter { * {@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"; @@ -128,6 +134,9 @@ public final class BluetoothAdapter { * {@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"; @@ -153,6 +162,24 @@ public final class BluetoothAdapter { 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. @@ -363,6 +390,39 @@ public final class BluetoothAdapter { 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 */ @@ -374,6 +434,7 @@ public final class BluetoothAdapter { /** @hide */ public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager"; + private final IBinder mToken; /** When creating a ServerSocket using listenUsingRfcommOn() or @@ -444,6 +505,7 @@ public final class BluetoothAdapter { } catch (RemoteException e) {Log.e(TAG, "", e);} mManagerService = managerService; mLeScanClients = new HashMap<LeScanCallback, ScanCallback>(); + mToken = new Binder(); } /** @@ -490,11 +552,9 @@ public final class BluetoothAdapter { * 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) { @@ -509,9 +569,7 @@ public final class BluetoothAdapter { * 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); @@ -529,7 +587,6 @@ public final class BluetoothAdapter { * @return true if the local adapter is turned on */ public boolean isEnabled() { - try { synchronized(mManagerCallback) { if (mService != null) return mService.isEnabled(); @@ -539,6 +596,178 @@ public final class BluetoothAdapter { } /** + * 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}, @@ -556,6 +785,13 @@ public final class BluetoothAdapter { { 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 @@ -567,6 +803,49 @@ public final class BluetoothAdapter { } /** + * 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 @@ -594,10 +873,23 @@ public final class BluetoothAdapter { * 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);} @@ -1558,6 +1850,10 @@ public final class BluetoothAdapter { } } } + + public void onBrEdrDown() { + if (VDBG) Log.i(TAG, "on QBrEdrDown: "); + } }; /** diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 1fdf9f4..1e82c03 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -606,7 +606,9 @@ public final class BluetoothDevice implements Parcelable { public void onBluetoothServiceUp(IBluetooth bluetoothService) throws RemoteException { synchronized (BluetoothDevice.class) { - sService = bluetoothService; + if (sService == null) { + sService = bluetoothService; + } } } @@ -616,6 +618,11 @@ public final class BluetoothDevice implements Parcelable { sService = null; } } + + public void onBrEdrDown() + { + if (DBG) Log.d(TAG, "onBrEdrDown: reached BLE ON state"); + } }; /** * Create a new BluetoothDevice @@ -1030,7 +1037,7 @@ public final class BluetoothDevice implements Parcelable { * 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 final class BluetoothDevice implements Parcelable { */ 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 @@ public final class BluetoothDevice implements Parcelable { 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 @@ public final class BluetoothDevice implements Parcelable { 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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index af560df..90df198 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -102,4 +102,6 @@ interface IBluetooth // for dumpsys support String dump(); + void onLeServiceUp(); + void onBrEdrDown(); } diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl index 7070bae..4ca57f8 100644 --- a/core/java/android/bluetooth/IBluetoothGatt.aidl +++ b/core/java/android/bluetooth/IBluetoothGatt.aidl @@ -101,4 +101,6 @@ interface IBluetoothGatt { 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/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl index 7411d3f..8d1ce99 100644 --- a/core/java/android/bluetooth/IBluetoothManager.aidl +++ b/core/java/android/bluetooth/IBluetoothManager.aidl @@ -44,4 +44,6 @@ interface IBluetoothManager String getAddress(); String getName(); + int updateBleAppCount(IBinder b, boolean enable); + boolean isBleAppPresent(); } diff --git a/core/java/android/bluetooth/IBluetoothManagerCallback.aidl b/core/java/android/bluetooth/IBluetoothManagerCallback.aidl index 9551086..1385daf 100644 --- a/core/java/android/bluetooth/IBluetoothManagerCallback.aidl +++ b/core/java/android/bluetooth/IBluetoothManagerCallback.aidl @@ -26,4 +26,5 @@ import android.bluetooth.IBluetooth; interface IBluetoothManagerCallback { void onBluetoothServiceUp(in IBluetooth bluetoothService); void onBluetoothServiceDown(); -}
\ No newline at end of file + void onBrEdrDown(); +} diff --git a/core/java/android/bluetooth/le/BluetoothLeUtils.java b/core/java/android/bluetooth/le/BluetoothLeUtils.java index 4916bd9..c40256b 100644 --- a/core/java/android/bluetooth/le/BluetoothLeUtils.java +++ b/core/java/android/bluetooth/le/BluetoothLeUtils.java @@ -132,7 +132,7 @@ public class BluetoothLeUtils { * {@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/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index ef51ad6..3e5eee8 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -112,6 +112,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { 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. @@ -184,11 +185,40 @@ class BluetoothManagerService extends IBluetoothManager.Stub { 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); } } @@ -203,12 +233,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { 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(); - } } } }; @@ -218,6 +242,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { mContext = context; mBluetooth = null; + mBluetoothGatt = null; mBinding = false; mUnbinding = false; mEnable = false; @@ -396,6 +421,133 @@ class BluetoothManagerService extends IBluetoothManager.Stub { 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 + @@ -445,11 +597,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub { 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; } @@ -508,6 +658,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } else { mUnbinding=false; } + mBluetoothGatt = null; } } @@ -1034,6 +1185,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { synchronized(mConnection) { if (msg.arg1 == SERVICE_IBLUETOOTHGATT) { mBluetoothGatt = IBluetoothGatt.Stub.asInterface(service); + onBluetoothGattServiceUp(); break; } // else must be SERVICE_IBLUETOOTH @@ -1111,12 +1263,18 @@ class BluetoothManagerService extends IBluetoothManager.Stub { 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"); @@ -1376,39 +1534,90 @@ class BluetoothManagerService extends IBluetoothManager.Stub { 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 (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_BLE_ON + || newState == BluetoothAdapter.STATE_OFF) { + boolean intermediate_off = (prevState == BluetoothAdapter.STATE_TURNING_OFF + && newState == BluetoothAdapter.STATE_BLE_ON); + + 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); + } } } |