Add Api to get profile connection state.

This gets the current connection state of the profile with respect
to the local Bluetooth adapter.

Change-Id: I7cff6c9201d29a8b45413cff7384b7483f21bd5c
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 28bc424..264db19 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -774,6 +774,31 @@
     }
 
     /**
+     * Get the current connection state of a profile.
+     * This function can be used to check whether the local Bluetooth adapter
+     * is connected to any remote device for a specific profile.
+     * Profile can be one of {@link BluetoothProfile.HEADSET},
+     * {@link BluetoothProfile.A2DP}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
+     *
+     * <p> Return value can be one of
+     * {@link * BluetoothProfile.STATE_DISCONNECTED},
+     * {@link * BluetoothProfile.STATE_CONNECTING},
+     * {@link * BluetoothProfile.STATE_CONNECTED},
+     * {@link * BluetoothProfile.STATE_DISCONNECTING}
+     * @hide
+     */
+    public int getProfileConnectionState(int profile) {
+        if (getState() != STATE_ON) return BluetoothProfile.STATE_DISCONNECTED;
+        try {
+            return mService.getProfileConnectionState(profile);
+        } catch (RemoteException e) {Log.e(TAG, "getProfileConnectionState:", e);}
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+    /**
      * Picks RFCOMM channels until none are left.
      * Avoids reserved channels.
      */
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 6cd81fd..58b3868 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -83,6 +83,12 @@
     public static final int PAN = 5;
 
     /**
+     * PBAP
+     * @hide
+     */
+    public static final int PBAP = 6;
+
+    /**
      * Default priority for devices that we try to auto-connect to and
      * and allow incoming connections for the profile
      * @hide
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 48dfed8..d4e7f7d 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -53,6 +53,7 @@
     byte[] readOutOfBandData();
 
     int getAdapterConnectionState();
+    int getProfileConnectionState(int profile);
     boolean changeApplicationBluetoothState(boolean on,
                                 in IBluetoothStateChangeCallback callback, in
                                 IBinder b);
@@ -121,5 +122,5 @@
     List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(in int[] states);
     int getHealthDeviceConnectionState(in BluetoothDevice device);
 
-    void sendConnectionStateChange(in BluetoothDevice device, int state, int prevState);
+    void sendConnectionStateChange(in BluetoothDevice device, int profile, int state, int prevState);
 }
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 8c04853..c4cb3a5 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -523,7 +523,8 @@
 
             if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state);
 
-            mBluetoothService.sendConnectionStateChange(device, state, prevState);
+            mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.A2DP, state,
+                                                        prevState);
         }
     }
 
diff --git a/core/java/android/server/BluetoothHealthProfileHandler.java b/core/java/android/server/BluetoothHealthProfileHandler.java
index 105ff33..eafd7bd 100644
--- a/core/java/android/server/BluetoothHealthProfileHandler.java
+++ b/core/java/android/server/BluetoothHealthProfileHandler.java
@@ -20,6 +20,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHealth;
 import android.bluetooth.BluetoothHealthAppConfiguration;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetoothHealthCallback;
 import android.content.Context;
 import android.os.Handler;
@@ -567,7 +568,8 @@
     private void updateAndSendIntent(BluetoothDevice device, int prevDeviceState,
             int newDeviceState) {
         mHealthDevices.put(device, newDeviceState);
-        mBluetoothService.sendConnectionStateChange(device, prevDeviceState, newDeviceState);
+        mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.HEALTH,
+                                                    prevDeviceState, newDeviceState);
     }
 
     /**
diff --git a/core/java/android/server/BluetoothInputProfileHandler.java b/core/java/android/server/BluetoothInputProfileHandler.java
index 247e297..31764b0 100644
--- a/core/java/android/server/BluetoothInputProfileHandler.java
+++ b/core/java/android/server/BluetoothInputProfileHandler.java
@@ -20,6 +20,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothDeviceProfileState;
 import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProfileState;
 import android.content.Context;
 import android.content.Intent;
@@ -191,7 +192,8 @@
         mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM);
 
         debugLog("InputDevice state : device: " + device + " State:" + prevState + "->" + state);
-        mBluetoothService.sendConnectionStateChange(device, state, prevState);
+        mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.INPUT_DEVICE, state,
+                                                    prevState);
     }
 
     void handleInputDevicePropertyChange(String address, boolean connected) {
diff --git a/core/java/android/server/BluetoothPanProfileHandler.java b/core/java/android/server/BluetoothPanProfileHandler.java
index 37cfdc4..bfad747 100644
--- a/core/java/android/server/BluetoothPanProfileHandler.java
+++ b/core/java/android/server/BluetoothPanProfileHandler.java
@@ -19,6 +19,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothTetheringDataTracker;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -303,7 +304,8 @@
         mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM);
 
         debugLog("Pan Device state : device: " + device + " State:" + prevState + "->" + state);
-        mBluetoothService.sendConnectionStateChange(device, state, prevState);
+        mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.PAN, state,
+                                                    prevState);
     }
 
     private class BluetoothPanDevice {
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index ee14673..9f435fd 100755
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -166,6 +166,7 @@
     private static final String INCOMING_CONNECTION_FILE =
       "/data/misc/bluetooth/incoming_connection.conf";
     private HashMap<String, Pair<Integer, String>> mIncomingConnections;
+    private HashMap<Integer, Pair<Integer, Integer>> mProfileConnectionState;
 
     private static class RemoteService {
         public String address;
@@ -237,6 +238,7 @@
         mBluetoothPanProfileHandler = BluetoothPanProfileHandler.getInstance(mContext, this);
         mBluetoothHealthProfileHandler = BluetoothHealthProfileHandler.getInstance(mContext, this);
         mIncomingConnections = new HashMap<String, Pair<Integer, String>>();
+        mProfileConnectionState = new HashMap<Integer, Pair<Integer, Integer>>();
     }
 
     public static synchronized String readDockBluetoothAddress() {
@@ -1742,6 +1744,19 @@
         dumpInputDeviceProfile(pw);
         dumpPanProfile(pw);
         dumpApplicationServiceRecords(pw);
+        dumpProfileState(pw);
+    }
+
+    private void dumpProfileState(PrintWriter pw) {
+        pw.println("\n--Profile State dump--");
+        pw.println("\n Headset profile state:" +
+                mAdapter.getProfileConnectionState(BluetoothProfile.HEADSET));
+        pw.println("\n A2dp profile state:" +
+                mAdapter.getProfileConnectionState(BluetoothProfile.A2DP));
+        pw.println("\n HID profile state:" +
+                mAdapter.getProfileConnectionState(BluetoothProfile.INPUT_DEVICE));
+        pw.println("\n PAN profile state:" +
+                mAdapter.getProfileConnectionState(BluetoothProfile.PAN));
     }
 
     private void dumpHeadsetService(PrintWriter pw) {
@@ -2443,23 +2458,85 @@
         return mAdapterConnectionState;
     }
 
-    public synchronized void sendConnectionStateChange(BluetoothDevice device, int state,
-                                                        int prevState) {
+    public int getProfileConnectionState(int profile) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+        Pair<Integer, Integer> state = mProfileConnectionState.get(profile);
+        if (state == null) return BluetoothProfile.STATE_DISCONNECTED;
+
+        return state.first;
+    }
+
+    private void updateProfileConnectionState(int profile, int newState, int oldState) {
+        // mProfileConnectionState is a hashmap -
+        // <Integer, Pair<Integer, Integer>>
+        // The key is the profile, the value is a pair. first element
+        // is the state and the second element is the number of devices
+        // in that state.
+        int numDev = 1;
+        int newHashState = newState;
+        boolean update = true;
+
+        // The following conditions are considered in this function:
+        // 1. If there is no record of profile and state - update
+        // 2. If a new device's state is current hash state - increment
+        //    number of devices in the state.
+        // 3. If a state change has happened to Connected or Connecting
+        //    (if current state is not connected), update.
+        // 4. If numDevices is 1 and that device state is being updated, update
+        // 5. If numDevices is > 1 and one of the devices is changing state,
+        //    decrement numDevices but maintain oldState if it is Connected or
+        //    Connecting
+        Pair<Integer, Integer> stateNumDev = mProfileConnectionState.get(profile);
+        if (stateNumDev != null) {
+            int currHashState = stateNumDev.first;
+            numDev = stateNumDev.second;
+
+            if (newState == currHashState) {
+                numDev ++;
+            } else if (newState == BluetoothProfile.STATE_CONNECTED ||
+                   (newState == BluetoothProfile.STATE_CONNECTING &&
+                    currHashState != BluetoothProfile.STATE_CONNECTED)) {
+                 numDev = 1;
+            } else if (numDev == 1 && oldState == currHashState) {
+                 update = true;
+            } else if (numDev > 1 && oldState == currHashState) {
+                 numDev --;
+
+                 if (currHashState == BluetoothProfile.STATE_CONNECTED ||
+                     currHashState == BluetoothProfile.STATE_CONNECTING) {
+                    newHashState = currHashState;
+                 }
+            } else {
+                 update = false;
+            }
+        }
+
+        if (update) {
+            mProfileConnectionState.put(profile, new Pair<Integer, Integer>(newHashState,
+                    numDev));
+        }
+    }
+
+    public synchronized void sendConnectionStateChange(BluetoothDevice
+            device, int profile, int state, int prevState) {
         // Since this is a binder call check if Bluetooth is on still
         if (getBluetoothStateInternal() == BluetoothAdapter.STATE_OFF) return;
 
-        if (updateCountersAndCheckForConnectionStateChange(state, prevState)) {
-            if (!validateProfileConnectionState(state) ||
-                    !validateProfileConnectionState(prevState)) {
-                // Previously, an invalid state was broadcast anyway,
-                // with the invalid state converted to -1 in the intent.
-                // Better to log an error and not send an intent with
-                // invalid contents or set mAdapterConnectionState to -1.
-                Log.e(TAG, "Error in sendConnectionStateChange: "
-                        + "prevState " + prevState + " state " + state);
-                return;
-            }
+        if (!validateProfileConnectionState(state) ||
+                !validateProfileConnectionState(prevState)) {
+            // Previously, an invalid state was broadcast anyway,
+            // with the invalid state converted to -1 in the intent.
+            // Better to log an error and not send an intent with
+            // invalid contents or set mAdapterConnectionState to -1.
+            Log.e(TAG, "Error in sendConnectionStateChange: "
+                    + "prevState " + prevState + " state " + state);
+            return;
+        }
 
+        updateProfileConnectionState(profile, state, prevState);
+
+        if (updateCountersAndCheckForConnectionStateChange(state, prevState)) {
             mAdapterConnectionState = state;
 
             if (state == BluetoothProfile.STATE_DISCONNECTED) {