Cache the remote device's service channel.

Bluez Device implementation is such that when a device
is unpaired, we removes the device and hence there is no
way to interact with it unless you pair again. Remote service
channel call is used to get the rfcomm channel number which
will be used in profiles like OPP which don't require pairing.

Change-Id: I868a6cdfdb1b7d3591dd8b66cd0320f41a9c1b92
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 0596b21..24ad06a 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -83,6 +83,12 @@
      * @param uuid
      */
     public static boolean isUuidPresent(ParcelUuid[] uuidArray, ParcelUuid uuid) {
+        if ((uuidArray == null || uuidArray.length == 0) && uuid == null)
+            return true;
+
+        if (uuidArray == null)
+            return false;
+
         for (ParcelUuid element: uuidArray) {
             if (element.equals(uuid)) return true;
         }
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 34921f4..f9ab31c 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -369,6 +369,10 @@
                 uuid = str.toString();
             }
             mBluetoothService.setRemoteDeviceProperty(address, name, uuid);
+
+            // UUIDs have changed, query remote service channel and update cache.
+            mBluetoothService.updateDeviceServiceChannelCache(address);
+
             mBluetoothService.sendUuidIntent(address);
         } else if (name.equals("Paired")) {
             if (propValues[1].equals("true")) {
@@ -537,8 +541,7 @@
         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
         // We don't parse the xml here, instead just query Bluez for the properties.
         if (result) {
-            String[] properties = mBluetoothService.getRemoteDeviceProperties(address);
-            mBluetoothService.addRemoteDeviceProperties(address, properties);
+            mBluetoothService.updateRemoteDevicePropertiesCache(address);
         }
         mBluetoothService.sendUuidIntent(address);
     }
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 26007c5..253b1ab 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -28,6 +28,7 @@
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetooth;
 import android.bluetooth.ParcelUuid;
 import android.content.BroadcastReceiver;
@@ -85,6 +86,7 @@
     private final Map<String, String> mAdapterProperties;
     private final HashMap <String, Map<String, String>> mDeviceProperties;
 
+    private final HashMap <String, Map<ParcelUuid, Integer>> mDeviceServiceChannelCache;
     private final ArrayList <String> mUuidIntentTracker;
 
     static {
@@ -111,6 +113,8 @@
         mIsDiscovering = false;
         mAdapterProperties = new HashMap<String, String>();
         mDeviceProperties = new HashMap<String, Map<String,String>>();
+
+        mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>();
         mUuidIntentTracker = new ArrayList<String>();
         registerForAirplaneMode();
     }
@@ -880,16 +884,22 @@
             // Query for remote device properties, again.
             // We will need to reload the cache when we switch Bluetooth on / off
             // or if we crash.
-            String[] propValues = getRemoteDeviceProperties(address);
-            if (propValues != null) {
-                addRemoteDeviceProperties(address, propValues);
+            if (updateRemoteDevicePropertiesCache(address))
                 return getRemoteDeviceProperty(address, property);
-            }
         }
         Log.e(TAG, "getRemoteDeviceProperty: " + property + "not present:" + address);
         return null;
     }
 
+    /* package */ synchronized boolean updateRemoteDevicePropertiesCache(String address) {
+        String[] propValues = getRemoteDeviceProperties(address);
+        if (propValues != null) {
+            addRemoteDeviceProperties(address, propValues);
+            return true;
+        }
+        return false;
+    }
+
     /* package */ synchronized void addRemoteDeviceProperties(String address, String[] properties) {
         /*
          * We get a DeviceFound signal every time RSSI changes or name changes.
@@ -924,6 +934,10 @@
             propertyValues.put(name, newValue);
         }
         mDeviceProperties.put(address, propertyValues);
+
+        // We have added a new remote device or updated its properties.
+        // Also update the serviceChannel cache.
+        updateDeviceServiceChannelCache(address);
     }
 
     /* package */ void removeRemoteDeviceProperties(String address) {
@@ -1066,14 +1080,23 @@
      * @param uuid ParcelUuid of the service attribute
      *
      * @return rfcomm channel associated with the service attribute
+     *         -1 on error
      */
     public int getRemoteServiceChannel(String address, ParcelUuid uuid) {
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             return BluetoothDevice.ERROR;
         }
-        return getDeviceServiceChannelNative(getObjectPathFromAddress(address), uuid.toString(),
-                0x0004);
+        // Check if we are recovering from a crash.
+        if (mDeviceProperties.isEmpty()) {
+            if (!updateRemoteDevicePropertiesCache(address))
+                return -1;
+        }
+
+        Map<ParcelUuid, Integer> value = mDeviceServiceChannelCache.get(address);
+        if (value != null && value.containsKey(uuid))
+            return value.get(uuid);
+        return -1;
     }
 
     public synchronized boolean setPin(String address, byte[] pin) {
@@ -1152,6 +1175,27 @@
         return cancelPairingUserInputNative(address, data.intValue());
     }
 
+    public void updateDeviceServiceChannelCache(String address) {
+        ParcelUuid[] deviceUuids = getRemoteUuids(address);
+        // We are storing the rfcomm channel numbers only for the uuids
+        // we are interested in.
+        int channel;
+        ParcelUuid[] interestedUuids = {BluetoothUuid.Handsfree,
+                                        BluetoothUuid.HSP,
+                                        BluetoothUuid.ObexObjectPush};
+
+        Map <ParcelUuid, Integer> value = new HashMap<ParcelUuid, Integer>();
+        for (ParcelUuid uuid: interestedUuids) {
+            if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
+                channel =
+                   getDeviceServiceChannelNative(getObjectPathFromAddress(address), uuid.toString(),
+                                                 0x0004);
+                value.put(uuid, channel);
+            }
+        }
+        mDeviceServiceChannelCache.put(address, value);
+    }
+
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -1366,4 +1410,5 @@
     private native boolean setDevicePropertyBooleanNative(String objectPath, String key, int value);
     private native boolean createDeviceNative(String address);
     private native boolean discoverServicesNative(String objectPath, String pattern);
+
 }