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&mdash;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);
+            }
         }
     }