b/2126036 Improve remote device capability identification by switching to use UUIDs instead of class bits.

Change-Id: Ie60d1c579e40027c2174215c1989887a3250c9bc
diff --git a/src/com/android/settings/bluetooth/BluetoothEventRedirector.java b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java
index 1a373dd..9aae8b1 100644
--- a/src/com/android/settings/bluetooth/BluetoothEventRedirector.java
+++ b/src/com/android/settings/bluetooth/BluetoothEventRedirector.java
@@ -37,16 +37,13 @@
  */
 public class BluetoothEventRedirector {
     private static final String TAG = "BluetoothEventRedirector";
-    private static final boolean V = LocalBluetoothManager.V;
 
     private LocalBluetoothManager mManager;
 
     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (V) {
-                Log.v(TAG, "Received " + intent.getAction());
-            }
+            Log.v(TAG, "Received " + intent.getAction());
 
             String action = intent.getAction();
             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
@@ -65,6 +62,8 @@
                 short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
                 BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
                 String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
+                // TODO Pick up UUID. They should be available for 2.1 devices.
+                // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
                 mManager.getCachedDeviceManager().onDeviceAppeared(device, rssi, btClass, name);
 
             } else if (action.equals(BluetoothDevice.ACTION_DISAPPEARED)) {
@@ -107,6 +106,9 @@
             } else if (action.equals(BluetoothDevice.ACTION_CLASS_CHANGED)) {
                 mManager.getCachedDeviceManager().onBtClassChanged(device);
 
+            } else if (action.equals(BluetoothDevice.ACTION_UUID)) {
+                mManager.getCachedDeviceManager().onUuidChanged(device);
+
             } else if (action.equals(BluetoothDevice.ACTION_PAIRING_CANCEL)) {
                 int errorMsg = R.string.bluetooth_pairing_error_message;
                 mManager.showError(device, R.string.bluetooth_error_title, errorMsg);
@@ -139,6 +141,7 @@
         filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
         filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
         filter.addAction(BluetoothDevice.ACTION_CLASS_CHANGED);
+        filter.addAction(BluetoothDevice.ACTION_UUID);
 
         mManager.getContext().registerReceiver(mBroadcastReceiver, filter);
     }
diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
index fdba11b..e70f85f 100644
--- a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
+++ b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
@@ -17,13 +17,15 @@
 package com.android.settings.bluetooth;
 
 import android.app.AlertDialog;
-import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.ParcelUuid;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.ContextMenu;
@@ -50,6 +52,7 @@
     private static final String TAG = "CachedBluetoothDevice";
     private static final boolean D = LocalBluetoothManager.D;
     private static final boolean V = LocalBluetoothManager.V;
+    private static final boolean DEBUG = true; // STOPSHIP - disable before final rom
 
     private static final int CONTEXT_ITEM_CONNECT = Menu.FIRST + 1;
     private static final int CONTEXT_ITEM_DISCONNECT = Menu.FIRST + 2;
@@ -75,6 +78,17 @@
      */
     private boolean mIsConnectingErrorPossible;
 
+    /**
+     * Last time a bt profile auto-connect was attempted without any profiles or
+     * UUIDs. If an ACTION_UUID intent comes in within
+     * MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect
+     * again with the new UUIDs
+     */
+    private long mConnectAttemptedWithoutUuid;
+
+    // See mConnectAttemptedWithoutUuid
+    private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000;
+
     // Max time to hold the work queue if we don't get or missed a response
     // from the bt framework.
     private static final long MAX_WAIT_TIME_FOR_FRAMEWORK = 25 * 1000;
@@ -361,6 +375,16 @@
     public void connect() {
         if (!ensurePaired()) return;
 
+        // Try to initialize the profiles if there were not.
+        if (mProfiles.size() == 0) {
+            if (!updateProfiles()) {
+                // If UUIDs are not available yet, connect will be happen
+                // upon arrival of the ACTION_UUID intent.
+                mConnectAttemptedWithoutUuid = SystemClock.elapsedRealtime();
+                return;
+            }
+        }
+
         // Reset the only-show-one-error-dialog tracking variable
         mIsConnectingErrorPossible = true;
 
@@ -479,6 +503,7 @@
     private void fillData() {
         fetchName();
         fetchBtClass();
+        updateProfiles();
 
         mVisible = false;
 
@@ -599,9 +624,47 @@
      */
     private void fetchBtClass() {
         mBtClass = mDevice.getBluetoothClass();
-        if (mBtClass != null) {
-            LocalBluetoothProfileManager.fill(mBtClass, mProfiles);
+    }
+
+    private boolean updateProfiles() {
+        ParcelUuid[] uuids = mDevice.getUuids();
+        if (uuids == null) return false;
+
+        LocalBluetoothProfileManager.updateProfiles(uuids, mProfiles);
+
+        if (DEBUG) {
+            Log.e(TAG, "updating profiles for " + mDevice.getName());
+
+            boolean printUuids = true;
+            BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
+
+            if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET) !=
+                mProfiles.contains(Profile.HEADSET)) {
+                Log.v(TAG, "headset classbits != uuid");
+                printUuids = true;
+            }
+
+            if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_A2DP) !=
+                mProfiles.contains(Profile.A2DP)) {
+                Log.v(TAG, "a2dp classbits != uuid");
+                printUuids = true;
+            }
+
+            if (bluetoothClass.doesClassMatch(BluetoothClass.PROFILE_OPP) !=
+                mProfiles.contains(Profile.OPP)) {
+                Log.v(TAG, "opp classbits != uuid");
+                printUuids = true;
+            }
+
+            if (printUuids) {
+                Log.v(TAG, "Class: " + bluetoothClass.toString());
+                Log.v(TAG, "UUID:");
+                for (int i = 0; i < uuids.length; i++) {
+                    Log.v(TAG, "  " + uuids[i]);
+                }
+            }
         }
+        return true;
     }
 
     /**
@@ -613,10 +676,30 @@
         dispatchAttributesChanged();
     }
 
+    /**
+     * Refreshes the UI when framework alerts us of a UUID change.
+     */
+    public void onUuidChanged() {
+        updateProfiles();
+
+        if (DEBUG) Log.e(TAG, "onUuidChanged: Time since last connect w/ no uuid "
+                + (SystemClock.elapsedRealtime() - mConnectAttemptedWithoutUuid));
+
+        /*
+         * If a connect was attempted earlier without any UUID, we will do the
+         * connect now.
+         */
+        if (mProfiles.size() > 0
+                && (mConnectAttemptedWithoutUuid + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock
+                        .elapsedRealtime()) {
+            connect();
+        }
+        dispatchAttributesChanged();
+    }
+
     public void setBtClass(BluetoothClass btClass) {
         if (btClass != null && mBtClass != btClass) {
             mBtClass = btClass;
-            LocalBluetoothProfileManager.fill(mBtClass, mProfiles);
             dispatchAttributesChanged();
         }
     }
diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java b/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java
index 046cd76..b7cea23 100644
--- a/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java
+++ b/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java
@@ -257,4 +257,11 @@
             cachedDevice.refreshBtClass();
         }
     }
+
+    public synchronized void onUuidChanged(BluetoothDevice device) {
+        CachedBluetoothDevice cachedDevice = findDevice(device);
+        if (cachedDevice != null) {
+            cachedDevice.onUuidChanged();
+        }
+    }
 }
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
index a59d229..2ea1fe7 100644
--- a/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
+++ b/src/com/android/settings/bluetooth/LocalBluetoothProfileManager.java
@@ -18,10 +18,10 @@
 
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.ParcelUuid;
 import android.os.Handler;
-import android.text.TextUtils;
 
 import com.android.settings.R;
 
@@ -35,27 +35,42 @@
  * functionality related to a profile.
  */
 public abstract class LocalBluetoothProfileManager {
+    private static final String TAG = "LocalBluetoothProfileManager";
+
+    private static final ParcelUuid[] HEADSET_PROFILE_UUIDS = new ParcelUuid[] {
+        BluetoothUuid.HSP,
+        BluetoothUuid.Handsfree,
+    };
+
+    private static final ParcelUuid[] A2DP_PROFILE_UUIDS = new ParcelUuid[] {
+        BluetoothUuid.AudioSink,
+        BluetoothUuid.AdvAudioDist,
+    };
+
+    private static final ParcelUuid[] OPP_PROFILE_UUIDS = new ParcelUuid[] {
+        BluetoothUuid.ObexObjectPush
+    };
 
     // TODO: close profiles when we're shutting down
     private static Map<Profile, LocalBluetoothProfileManager> sProfileMap =
-            new HashMap<Profile, LocalBluetoothProfileManager>(); 
-    
+            new HashMap<Profile, LocalBluetoothProfileManager>();
+
     protected LocalBluetoothManager mLocalManager;
-    
+
     public static LocalBluetoothProfileManager getProfileManager(LocalBluetoothManager localManager,
             Profile profile) {
-        
+
         LocalBluetoothProfileManager profileManager;
-        
+
         synchronized (sProfileMap) {
             profileManager = sProfileMap.get(profile);
-            
+
             if (profileManager == null) {
                 switch (profile) {
                 case A2DP:
                     profileManager = new A2dpProfileManager(localManager);
                     break;
-                    
+
                 case HEADSET:
                     profileManager = new HeadsetProfileManager(localManager);
                     break;
@@ -64,35 +79,38 @@
                     profileManager = new OppProfileManager(localManager);
                     break;
                 }
-                
-                sProfileMap.put(profile, profileManager);    
+
+                sProfileMap.put(profile, profileManager);
             }
         }
-        
+
         return profileManager;
     }
 
     /**
      * Temporary method to fill profiles based on a device's class.
-     * 
+     *
      * NOTE: This list happens to define the connection order. We should put this logic in a more
      * well known place when this method is no longer temporary.
-     * 
-     * @param btClass The class
+     * @param uuids of the remote device
      * @param profiles The list of profiles to fill
      */
-    public static void fill(BluetoothClass btClass, List<Profile> profiles) {
+    public static void updateProfiles(ParcelUuid[] uuids, List<Profile> profiles) {
         profiles.clear();
 
-        if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
+        if (uuids == null) {
+            return;
+        }
+
+        if (BluetoothUuid.containsAnyUuid(uuids, HEADSET_PROFILE_UUIDS)) {
             profiles.add(Profile.HEADSET);
         }
 
-        if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
+        if (BluetoothUuid.containsAnyUuid(uuids, A2DP_PROFILE_UUIDS)) {
             profiles.add(Profile.A2DP);
         }
 
-        if (btClass.doesClassMatch(BluetoothClass.PROFILE_OPP)) {
+        if (BluetoothUuid.containsAnyUuid(uuids, OPP_PROFILE_UUIDS)) {
             profiles.add(Profile.OPP);
         }
     }
@@ -100,17 +118,17 @@
     protected LocalBluetoothProfileManager(LocalBluetoothManager localManager) {
         mLocalManager = localManager;
     }
-    
+
     public abstract boolean connect(BluetoothDevice device);
-    
+
     public abstract boolean disconnect(BluetoothDevice device);
-    
+
     public abstract int getConnectionStatus(BluetoothDevice device);
 
     public abstract int getSummary(BluetoothDevice device);
 
     public abstract int convertState(int a2dpState);
-    
+
     public abstract boolean isPreferred(BluetoothDevice device);
 
     public abstract void setPreferred(BluetoothDevice device, boolean preferred);
@@ -118,26 +136,26 @@
     public boolean isConnected(BluetoothDevice device) {
         return SettingsBtStatus.isConnectionStatusConnected(getConnectionStatus(device));
     }
-    
+
     // TODO: int instead of enum
     public enum Profile {
         HEADSET(R.string.bluetooth_profile_headset),
         A2DP(R.string.bluetooth_profile_a2dp),
         OPP(R.string.bluetooth_profile_opp);
-        
+
         public final int localizedString;
-        
+
         private Profile(int localizedString) {
             this.localizedString = localizedString;
         }
     }
 
     /**
-     * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service. 
+     * A2dpProfileManager is an abstraction for the {@link BluetoothA2dp} service.
      */
     private static class A2dpProfileManager extends LocalBluetoothProfileManager {
         private BluetoothA2dp mService;
-        
+
         public A2dpProfileManager(LocalBluetoothManager localManager) {
             super(localManager);
             mService = new BluetoothA2dp(localManager.getContext());
@@ -158,12 +176,12 @@
         public boolean disconnect(BluetoothDevice device) {
             return mService.disconnectSink(device);
         }
-        
+
         @Override
         public int getConnectionStatus(BluetoothDevice device) {
             return convertState(mService.getSinkState(device));
         }
-        
+
         @Override
         public int getSummary(BluetoothDevice device) {
             int connectionStatus = getConnectionStatus(device);
@@ -204,15 +222,15 @@
             }
         }
     }
-    
+
     /**
-     * HeadsetProfileManager is an abstraction for the {@link BluetoothHeadset} service. 
+     * HeadsetProfileManager is an abstraction for the {@link BluetoothHeadset} service.
      */
     private static class HeadsetProfileManager extends LocalBluetoothProfileManager
             implements BluetoothHeadset.ServiceListener {
         private BluetoothHeadset mService;
         private Handler mUiHandler = new Handler();
-        
+
         public HeadsetProfileManager(LocalBluetoothManager localManager) {
             super(localManager);
             mService = new BluetoothHeadset(localManager.getContext(), this);
@@ -262,11 +280,11 @@
                     ? convertState(mService.getState())
                     : SettingsBtStatus.CONNECTION_STATUS_DISCONNECTED;
         }
-        
+
         @Override
         public int getSummary(BluetoothDevice device) {
             int connectionStatus = getConnectionStatus(device);
-            
+
             if (SettingsBtStatus.isConnectionStatusConnected(connectionStatus)) {
                 return R.string.bluetooth_headset_profile_summary_connected;
             } else {