am cf28f9b8: am 4ae97d36: Post broadcasts to bg and cache bt state info

* commit 'cf28f9b8830503605ff254a5ca8edd368e065a93':
  Post broadcasts to bg and cache bt state info
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 8b328aa..d6647af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -69,9 +69,11 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -368,6 +370,7 @@
     private ScreenPinningRequest mScreenPinningRequest;
 
     private int mNavigationIconHints = 0;
+    private HandlerThread mHandlerThread;
 
     // ensure quick settings is disabled until the current user makes it through the setup wizard
     private boolean mUserSetup = false;
@@ -782,6 +785,10 @@
         // set the inital view visibility
         setAreThereNotifications();
 
+        // Background thread for any controllers that need it.
+        mHandlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
+        mHandlerThread.start();
+
         // Other icons
         mLocationController = new LocationControllerImpl(mContext); // will post a notification
         mBatteryController = new BatteryController(mContext);
@@ -800,7 +807,7 @@
         });
         mNetworkController = new NetworkControllerImpl(mContext);
         mHotspotController = new HotspotControllerImpl(mContext);
-        mBluetoothController = new BluetoothControllerImpl(mContext);
+        mBluetoothController = new BluetoothControllerImpl(mContext, mHandlerThread.getLooper());
         mSecurityController = new SecurityControllerImpl(mContext);
         if (mContext.getResources().getBoolean(R.bool.config_showRotationLock)) {
             mRotationLockController = new RotationLockControllerImpl(mContext);
@@ -3445,6 +3452,10 @@
             mWindowManager.removeViewImmediate(mNavigationBarView);
             mNavigationBarView = null;
         }
+        if (mHandlerThread != null) {
+            mHandlerThread.quitSafely();
+            mHandlerThread = null;
+        }
         mContext.unregisterReceiver(mBroadcastReceiver);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 6bd986b..81e1e45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -40,6 +40,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.ParcelUuid;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -51,7 +54,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Set;
 
 public class BluetoothControllerImpl implements BluetoothController {
@@ -68,6 +70,15 @@
         BluetoothProfile.STATE_CONNECTING,
         BluetoothProfile.STATE_CONNECTED,
     };
+    // Update all the BT device states.
+    private static final int MSG_UPDATE_CONNECTION_STATES = 1;
+    // Update just one BT device.
+    private static final int MSG_UPDATE_SINGLE_CONNECTION_STATE = 2;
+    // Update whether devices are bonded or not.
+    private static final int MSG_UPDATE_BONDED_DEVICES = 3;
+
+    private static final int MSG_ADD_PROFILE = 4;
+    private static final int MSG_REM_PROFILE = 5;
 
     private final Context mContext;
     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
@@ -76,12 +87,16 @@
     private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>();
     private final SparseArray<BluetoothProfile> mProfiles = new SparseArray<>();
 
+    private final H mHandler;
+
     private boolean mEnabled;
     private boolean mConnecting;
     private BluetoothDevice mLastDevice;
 
-    public BluetoothControllerImpl(Context context) {
+    public BluetoothControllerImpl(Context context, Looper bgLooper) {
         mContext = context;
+        mHandler = new H(bgLooper);
+
         final BluetoothManager bluetoothManager =
                 (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
         mAdapter = bluetoothManager.getAdapter();
@@ -92,7 +107,7 @@
 
         mReceiver.register();
         setAdapterState(mAdapter.getState());
-        updateBluetoothDevices();
+        updateBondedDevices();
         bindAllProfiles();
     }
 
@@ -116,8 +131,9 @@
 
     private static String infoToString(DeviceInfo info) {
         return info == null ? null : ("connectionState=" +
-                connectionStateToString(info.connectionState) + ",bonded=" + info.bonded
-                + ",profiles=" + profilesToString(info.connectedProfiles));
+                connectionStateToString(CONNECTION_STATES[info.connectionStateIndex])
+                + ",bonded=" + info.bonded + ",profiles="
+                + profilesToString(info.connectedProfiles));
     }
 
     private static String profilesToString(SparseArray<?> profiles) {
@@ -188,13 +204,14 @@
             paired.id = device.getAddress();
             paired.tag = device;
             paired.name = device.getAliasName();
-            paired.state = connectionStateToPairedDeviceState(info.connectionState);
+            paired.state = connectionStateToPairedDeviceState(info.connectionStateIndex);
             rt.add(paired);
         }
         return rt;
     }
 
-    private static int connectionStateToPairedDeviceState(int state) {
+    private static int connectionStateToPairedDeviceState(int index) {
+        int state = CONNECTION_STATES[index];
         if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED;
         if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING;
         if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING;
@@ -259,13 +276,31 @@
         return mLastDevice != null ? mLastDevice.getAliasName() : null;
     }
 
-    private void updateBluetoothDevices() {
+    private void updateBondedDevices() {
+        mHandler.removeMessages(MSG_UPDATE_BONDED_DEVICES);
+        mHandler.sendEmptyMessage(MSG_UPDATE_BONDED_DEVICES);
+    }
+
+    private void updateConnectionStates() {
+        mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES);
+        mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE);
+        mHandler.sendEmptyMessage(MSG_UPDATE_CONNECTION_STATES);
+    }
+
+    private void updateConnectionState(BluetoothDevice device, int profile, int state) {
+        if (mHandler.hasMessages(MSG_UPDATE_CONNECTION_STATES)) {
+            // If we are about to update all the devices, then we don't need to update this one.
+            return;
+        }
+        mHandler.obtainMessage(MSG_UPDATE_SINGLE_CONNECTION_STATE, profile, state, device)
+                .sendToTarget();
+    }
+
+    private void handleUpdateBondedDevices() {
         if (mAdapter == null) return;
         final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
         for (DeviceInfo info : mDeviceInfo.values()) {
             info.bonded = false;
-            info.connectionState = ERROR;
-            info.connectedProfiles.clear();
         }
         int bondedCount = 0;
         BluetoothDevice lastBonded = null;
@@ -279,32 +314,63 @@
                 }
             }
         }
-        final int N = mProfiles.size();
-        final int[] connectionType = new int[1];
-        for (int i = 0; i < CONNECTION_STATES.length; i++) {
-            connectionType[0] = CONNECTION_STATES[i];
-            for (int j = 0; j < N; j++) {
-                int profile = mProfiles.keyAt(j);
-                List<BluetoothDevice> devices = mProfiles.get(profile)
-                        .getDevicesMatchingConnectionStates(connectionType);
-                for (int k = 0; k < devices.size(); k++) {
-                    DeviceInfo info = mDeviceInfo.get(devices.get(k));
-                    if (info != null) {
-                        info.connectionState = CONNECTION_STATES[i];
-                        if (CONNECTION_STATES[i] == BluetoothProfile.STATE_CONNECTED) {
-                            info.connectedProfiles.put(profile, true);
-                        }
-                    }
-                }
-            }
-        }
         if (mLastDevice == null && bondedCount == 1) {
             mLastDevice = lastBonded;
         }
+        updateConnectionStates();
+        firePairedDevicesChanged();
+    }
+
+    private void handleUpdateConnectionStates() {
+        final int N = mDeviceInfo.size();
+        for (int i = 0; i < N; i++) {
+            BluetoothDevice device = mDeviceInfo.keyAt(i);
+            DeviceInfo info = updateInfo(device);
+            info.connectionStateIndex = 0;
+            info.connectedProfiles.clear();
+            for (int j = 0; j < mProfiles.size(); j++) {
+                int state = mProfiles.valueAt(j).getConnectionState(device);
+                handleUpdateConnectionState(device, mProfiles.keyAt(j), state);
+            }
+        }
+        handleConnectionChange();
+        firePairedDevicesChanged();
+    }
+
+    private void handleUpdateConnectionState(BluetoothDevice device, int profile, int state) {
+        if (DEBUG) Log.d(TAG, "updateConnectionState " + BluetoothUtil.deviceToString(device)
+                + " " + BluetoothUtil.profileToString(profile)
+                + " " + BluetoothUtil.connectionStateToString(state));
+        DeviceInfo info = updateInfo(device);
+        int stateIndex = 0;
+        for (int i = 0; i < CONNECTION_STATES.length; i++) {
+            if (CONNECTION_STATES[i] == state) {
+                stateIndex = i;
+                break;
+            }
+        }
+        info.profileStates.put(profile, stateIndex);
+
+        info.connectionStateIndex = 0;
+        final int N = info.profileStates.size();
+        for (int i = 0; i < N; i++) {
+            if (info.profileStates.valueAt(i) > info.connectionStateIndex) {
+                info.connectionStateIndex = info.profileStates.valueAt(i);
+            }
+        }
+        if (state == BluetoothProfile.STATE_CONNECTED) {
+            info.connectedProfiles.put(profile, true);
+        } else {
+            info.connectedProfiles.remove(profile);
+        }
+    }
+
+    private void handleConnectionChange() {
         // If we are no longer connected to the current device, see if we are connected to
         // something else, so we don't display a name we aren't connected to.
         if (mLastDevice != null &&
-                mDeviceInfo.get(mLastDevice).connectionState != BluetoothProfile.STATE_CONNECTED) {
+                CONNECTION_STATES[mDeviceInfo.get(mLastDevice).connectionStateIndex]
+                        != BluetoothProfile.STATE_CONNECTED) {
             // Make sure we don't keep this device while it isn't connected.
             mLastDevice = null;
             // Look for anything else connected.
@@ -312,13 +378,13 @@
             for (int i = 0; i < size; i++) {
                 BluetoothDevice device = mDeviceInfo.keyAt(i);
                 DeviceInfo info = mDeviceInfo.valueAt(i);
-                if (info.connectionState == BluetoothProfile.STATE_CONNECTED) {
+                if (CONNECTION_STATES[info.connectionStateIndex]
+                        == BluetoothProfile.STATE_CONNECTED) {
                     mLastDevice = device;
                     break;
                 }
             }
         }
-        firePairedDevicesChanged();
     }
 
     private void bindAllProfiles() {
@@ -366,17 +432,40 @@
         cb.onBluetoothStateChange(mEnabled, mConnecting);
     }
 
+    private static int getProfileFromAction(String action) {
+        if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+            return BluetoothProfile.A2DP;
+        } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+            return BluetoothProfile.HEADSET;
+        } else if (BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+            return BluetoothProfile.A2DP_SINK;
+        } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+            return BluetoothProfile.HEADSET_CLIENT;
+        } else if (BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+            return BluetoothProfile.INPUT_DEVICE;
+        } else if (BluetoothMap.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+            return BluetoothProfile.MAP;
+        } else if (BluetoothPan.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+            return BluetoothProfile.PAN;
+        }
+        if (DEBUG) Log.d(TAG, "Unknown action " + action);
+        return -1;
+    }
+
     private final ServiceListener mProfileListener = new ServiceListener() {
         @Override
         public void onServiceDisconnected(int profile) {
-            mProfiles.remove(profile);
-            updateBluetoothDevices();
+            if (DEBUG) Log.d(TAG, "Disconnected from " + BluetoothUtil.profileToString(profile));
+            // We lost a profile, don't do any updates until it gets removed.
+            mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES);
+            mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE);
+            mHandler.obtainMessage(MSG_REM_PROFILE, profile, 0).sendToTarget();
         }
 
         @Override
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            mProfiles.put(profile, proxy);
-            updateBluetoothDevices();
+            if (DEBUG) Log.d(TAG, "Connected to " + BluetoothUtil.profileToString(profile));
+            mHandler.obtainMessage(MSG_ADD_PROFILE, profile, 0, proxy).sendToTarget();
         }
     };
 
@@ -401,8 +490,10 @@
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
             final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                 setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR));
+                updateBondedDevices();
                 if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled);
             } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
                 updateInfo(device);
@@ -417,10 +508,17 @@
                 mLastDevice = device;
             } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
                 if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device);
-                // we'll update all bonded devices below
+                updateBondedDevices();
+            } else {
+                int profile = getProfileFromAction(intent.getAction());
+                int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+                if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGE "
+                        + BluetoothUtil.profileToString(profile)
+                        + " " + BluetoothUtil.connectionStateToString(state));
+                if ((profile != -1) && (state != -1)) {
+                    updateConnectionState(device, profile, state);
+                }
             }
-            // Always update bluetooth devices state.
-            updateBluetoothDevices();
         }
     }
 
@@ -431,9 +529,44 @@
         return info;
     }
 
+    private class H extends Handler {
+        public H(Looper l) {
+            super(l);
+        }
+
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE_CONNECTION_STATES:
+                    handleUpdateConnectionStates();
+                    firePairedDevicesChanged();
+                    break;
+                case MSG_UPDATE_SINGLE_CONNECTION_STATE:
+                    handleUpdateConnectionState((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
+                    handleConnectionChange();
+                    firePairedDevicesChanged();
+                    break;
+                case MSG_UPDATE_BONDED_DEVICES:
+                    handleUpdateBondedDevices();
+                    firePairedDevicesChanged();
+                    break;
+                case MSG_ADD_PROFILE:
+                    mProfiles.put(msg.arg1, (BluetoothProfile) msg.obj);
+                    handleUpdateConnectionStates();
+                    firePairedDevicesChanged();
+                    break;
+                case MSG_REM_PROFILE:
+                    mProfiles.remove(msg.arg1);
+                    handleUpdateConnectionStates();
+                    firePairedDevicesChanged();
+                    break;
+            }
+        };
+    };
+
     private static class DeviceInfo {
-        int connectionState = BluetoothAdapter.STATE_DISCONNECTED;
+        int connectionStateIndex = 0;
         boolean bonded;  // per getBondedDevices
         SparseArray<Boolean> connectedProfiles = new SparseArray<>();
+        SparseArray<Integer> profileStates = new SparseArray<>();
     }
 }