Home | History | Annotate | Download | only in policy
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.systemui.statusbar.policy;
     18 
     19 import static android.bluetooth.BluetoothAdapter.ERROR;
     20 import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString;
     21 import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString;
     22 import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString;
     23 import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile;
     24 import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString;
     25 import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString;
     26 
     27 import android.bluetooth.BluetoothA2dp;
     28 import android.bluetooth.BluetoothA2dpSink;
     29 import android.bluetooth.BluetoothAdapter;
     30 import android.bluetooth.BluetoothDevice;
     31 import android.bluetooth.BluetoothHeadset;
     32 import android.bluetooth.BluetoothHeadsetClient;
     33 import android.bluetooth.BluetoothInputDevice;
     34 import android.bluetooth.BluetoothManager;
     35 import android.bluetooth.BluetoothMap;
     36 import android.bluetooth.BluetoothPan;
     37 import android.bluetooth.BluetoothProfile;
     38 import android.bluetooth.BluetoothProfile.ServiceListener;
     39 import android.content.BroadcastReceiver;
     40 import android.content.Context;
     41 import android.content.Intent;
     42 import android.content.IntentFilter;
     43 import android.os.Handler;
     44 import android.os.Looper;
     45 import android.os.Message;
     46 import android.os.ParcelUuid;
     47 import android.util.ArrayMap;
     48 import android.util.ArraySet;
     49 import android.util.Log;
     50 import android.util.SparseArray;
     51 
     52 import com.android.systemui.statusbar.policy.BluetoothUtil.Profile;
     53 
     54 import java.io.FileDescriptor;
     55 import java.io.PrintWriter;
     56 import java.util.ArrayList;
     57 import java.util.Set;
     58 
     59 public class BluetoothControllerImpl implements BluetoothController {
     60     private static final String TAG = "BluetoothController";
     61     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     62     // This controls the order in which we check the states.  Since a device can only have
     63     // one state on screen, but can have multiple profiles, the later states override the
     64     // value of earlier states.  So if a device has a profile in CONNECTING and one in
     65     // CONNECTED, it will show as CONNECTED, theoretically this shouldn't really happen often,
     66     // but seemed worth noting.
     67     private static final int[] CONNECTION_STATES = {
     68         BluetoothProfile.STATE_DISCONNECTED,
     69         BluetoothProfile.STATE_DISCONNECTING,
     70         BluetoothProfile.STATE_CONNECTING,
     71         BluetoothProfile.STATE_CONNECTED,
     72     };
     73     // Update all the BT device states.
     74     private static final int MSG_UPDATE_CONNECTION_STATES = 1;
     75     // Update just one BT device.
     76     private static final int MSG_UPDATE_SINGLE_CONNECTION_STATE = 2;
     77     // Update whether devices are bonded or not.
     78     private static final int MSG_UPDATE_BONDED_DEVICES = 3;
     79 
     80     private static final int MSG_ADD_PROFILE = 4;
     81     private static final int MSG_REM_PROFILE = 5;
     82 
     83     private final Context mContext;
     84     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
     85     private final BluetoothAdapter mAdapter;
     86     private final Receiver mReceiver = new Receiver();
     87     private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>();
     88     private final SparseArray<BluetoothProfile> mProfiles = new SparseArray<>();
     89 
     90     private final H mHandler;
     91 
     92     private boolean mEnabled;
     93     private boolean mConnecting;
     94     private BluetoothDevice mLastDevice;
     95 
     96     public BluetoothControllerImpl(Context context, Looper bgLooper) {
     97         mContext = context;
     98         mHandler = new H(bgLooper);
     99 
    100         final BluetoothManager bluetoothManager =
    101                 (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
    102         mAdapter = bluetoothManager.getAdapter();
    103         if (mAdapter == null) {
    104             Log.w(TAG, "Default BT adapter not found");
    105             return;
    106         }
    107 
    108         mReceiver.register();
    109         setAdapterState(mAdapter.getState());
    110         updateBondedDevices();
    111         bindAllProfiles();
    112     }
    113 
    114     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    115         pw.println("BluetoothController state:");
    116         pw.print("  mAdapter="); pw.println(mAdapter);
    117         pw.print("  mEnabled="); pw.println(mEnabled);
    118         pw.print("  mConnecting="); pw.println(mConnecting);
    119         pw.print("  mLastDevice="); pw.println(mLastDevice);
    120         pw.print("  mCallbacks.size="); pw.println(mCallbacks.size());
    121         pw.print("  mProfiles="); pw.println(profilesToString(mProfiles));
    122         pw.print("  mDeviceInfo.size="); pw.println(mDeviceInfo.size());
    123         for (int i = 0; i < mDeviceInfo.size(); i++) {
    124             final BluetoothDevice device = mDeviceInfo.keyAt(i);
    125             final DeviceInfo info = mDeviceInfo.valueAt(i);
    126             pw.print("    "); pw.print(deviceToString(device));
    127             pw.print('('); pw.print(uuidsToString(device)); pw.print(')');
    128             pw.print("    "); pw.println(infoToString(info));
    129         }
    130     }
    131 
    132     private static String infoToString(DeviceInfo info) {
    133         return info == null ? null : ("connectionState=" +
    134                 connectionStateToString(CONNECTION_STATES[info.connectionStateIndex])
    135                 + ",bonded=" + info.bonded + ",profiles="
    136                 + profilesToString(info.connectedProfiles));
    137     }
    138 
    139     private static String profilesToString(SparseArray<?> profiles) {
    140         final int N = profiles.size();
    141         final StringBuffer buffer = new StringBuffer();
    142         buffer.append('[');
    143         for (int i = 0; i < N; i++) {
    144             if (i != 0) {
    145                 buffer.append(',');
    146             }
    147             buffer.append(BluetoothUtil.profileToString(profiles.keyAt(i)));
    148         }
    149         buffer.append(']');
    150         return buffer.toString();
    151     }
    152 
    153     public void addStateChangedCallback(Callback cb) {
    154         mCallbacks.add(cb);
    155         fireStateChange(cb);
    156     }
    157 
    158     @Override
    159     public void removeStateChangedCallback(Callback cb) {
    160         mCallbacks.remove(cb);
    161     }
    162 
    163     @Override
    164     public boolean isBluetoothEnabled() {
    165         return mAdapter != null && mAdapter.isEnabled();
    166     }
    167 
    168     @Override
    169     public boolean isBluetoothConnected() {
    170         return mAdapter != null
    171                 && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED;
    172     }
    173 
    174     @Override
    175     public boolean isBluetoothConnecting() {
    176         return mAdapter != null
    177                 && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING;
    178     }
    179 
    180     @Override
    181     public void setBluetoothEnabled(boolean enabled) {
    182         if (mAdapter != null) {
    183             if (enabled) {
    184                 mAdapter.enable();
    185             } else {
    186                 mAdapter.disable();
    187             }
    188         }
    189     }
    190 
    191     @Override
    192     public boolean isBluetoothSupported() {
    193         return mAdapter != null;
    194     }
    195 
    196     @Override
    197     public ArraySet<PairedDevice> getPairedDevices() {
    198         final ArraySet<PairedDevice> rt = new ArraySet<>();
    199         for (int i = 0; i < mDeviceInfo.size(); i++) {
    200             final BluetoothDevice device = mDeviceInfo.keyAt(i);
    201             final DeviceInfo info = mDeviceInfo.valueAt(i);
    202             if (!info.bonded) continue;
    203             final PairedDevice paired = new PairedDevice();
    204             paired.id = device.getAddress();
    205             paired.tag = device;
    206             paired.name = device.getAliasName();
    207             paired.state = connectionStateToPairedDeviceState(info.connectionStateIndex);
    208             rt.add(paired);
    209         }
    210         return rt;
    211     }
    212 
    213     private static int connectionStateToPairedDeviceState(int index) {
    214         int state = CONNECTION_STATES[index];
    215         if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED;
    216         if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING;
    217         if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING;
    218         return PairedDevice.STATE_DISCONNECTED;
    219     }
    220 
    221     @Override
    222     public void connect(final PairedDevice pd) {
    223         connect(pd, true);
    224     }
    225 
    226     @Override
    227     public void disconnect(PairedDevice pd) {
    228         connect(pd, false);
    229     }
    230 
    231     private void connect(PairedDevice pd, final boolean connect) {
    232         if (mAdapter == null || pd == null || pd.tag == null) return;
    233         final BluetoothDevice device = (BluetoothDevice) pd.tag;
    234         final DeviceInfo info = mDeviceInfo.get(device);
    235         final String action = connect ? "connect" : "disconnect";
    236         if (DEBUG) Log.d(TAG, action + " " + deviceToString(device));
    237         final ParcelUuid[] uuids = device.getUuids();
    238         if (uuids == null) {
    239             Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device));
    240             return;
    241         }
    242         SparseArray<Boolean> profiles = new SparseArray<>();
    243         if (connect) {
    244             // When connecting add every profile we can recognize by uuid.
    245             for (ParcelUuid uuid : uuids) {
    246                 final int profile = uuidToProfile(uuid);
    247                 if (profile == 0) {
    248                     Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: "
    249                             + uuidToString(uuid));
    250                     continue;
    251                 }
    252                 final boolean connected = info.connectedProfiles.get(profile, false);
    253                 if (!connected) {
    254                     profiles.put(profile, true);
    255                 }
    256             }
    257         } else {
    258             // When disconnecting, just add every profile we know they are connected to.
    259             profiles = info.connectedProfiles;
    260         }
    261         for (int i = 0; i < profiles.size(); i++) {
    262             final int profile = profiles.keyAt(i);
    263             if (mProfiles.indexOfKey(profile) >= 0) {
    264                 final Profile p = BluetoothUtil.getProfile(mProfiles.get(profile));
    265                 final boolean ok = connect ? p.connect(device) : p.disconnect(device);
    266                 if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
    267                         + (ok ? "succeeded" : "failed"));
    268             } else {
    269                 Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
    270             }
    271         }
    272     }
    273 
    274     @Override
    275     public String getLastDeviceName() {
    276         return mLastDevice != null ? mLastDevice.getAliasName() : null;
    277     }
    278 
    279     private void updateBondedDevices() {
    280         mHandler.removeMessages(MSG_UPDATE_BONDED_DEVICES);
    281         mHandler.sendEmptyMessage(MSG_UPDATE_BONDED_DEVICES);
    282     }
    283 
    284     private void updateConnectionStates() {
    285         mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES);
    286         mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE);
    287         mHandler.sendEmptyMessage(MSG_UPDATE_CONNECTION_STATES);
    288     }
    289 
    290     private void updateConnectionState(BluetoothDevice device, int profile, int state) {
    291         if (mHandler.hasMessages(MSG_UPDATE_CONNECTION_STATES)) {
    292             // If we are about to update all the devices, then we don't need to update this one.
    293             return;
    294         }
    295         mHandler.obtainMessage(MSG_UPDATE_SINGLE_CONNECTION_STATE, profile, state, device)
    296                 .sendToTarget();
    297     }
    298 
    299     private void handleUpdateBondedDevices() {
    300         if (mAdapter == null) return;
    301         final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
    302         for (DeviceInfo info : mDeviceInfo.values()) {
    303             info.bonded = false;
    304         }
    305         int bondedCount = 0;
    306         BluetoothDevice lastBonded = null;
    307         if (bondedDevices != null) {
    308             for (BluetoothDevice bondedDevice : bondedDevices) {
    309                 final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE;
    310                 updateInfo(bondedDevice).bonded = bonded;
    311                 if (bonded) {
    312                     bondedCount++;
    313                     lastBonded = bondedDevice;
    314                 }
    315             }
    316         }
    317         if (mLastDevice == null && bondedCount == 1) {
    318             mLastDevice = lastBonded;
    319         }
    320         updateConnectionStates();
    321         firePairedDevicesChanged();
    322     }
    323 
    324     private void handleUpdateConnectionStates() {
    325         final int N = mDeviceInfo.size();
    326         for (int i = 0; i < N; i++) {
    327             BluetoothDevice device = mDeviceInfo.keyAt(i);
    328             DeviceInfo info = updateInfo(device);
    329             info.connectionStateIndex = 0;
    330             info.connectedProfiles.clear();
    331             for (int j = 0; j < mProfiles.size(); j++) {
    332                 int state = mProfiles.valueAt(j).getConnectionState(device);
    333                 handleUpdateConnectionState(device, mProfiles.keyAt(j), state);
    334             }
    335         }
    336         handleConnectionChange();
    337         firePairedDevicesChanged();
    338     }
    339 
    340     private void handleUpdateConnectionState(BluetoothDevice device, int profile, int state) {
    341         if (DEBUG) Log.d(TAG, "updateConnectionState " + BluetoothUtil.deviceToString(device)
    342                 + " " + BluetoothUtil.profileToString(profile)
    343                 + " " + BluetoothUtil.connectionStateToString(state));
    344         DeviceInfo info = updateInfo(device);
    345         int stateIndex = 0;
    346         for (int i = 0; i < CONNECTION_STATES.length; i++) {
    347             if (CONNECTION_STATES[i] == state) {
    348                 stateIndex = i;
    349                 break;
    350             }
    351         }
    352         info.profileStates.put(profile, stateIndex);
    353 
    354         info.connectionStateIndex = 0;
    355         final int N = info.profileStates.size();
    356         for (int i = 0; i < N; i++) {
    357             if (info.profileStates.valueAt(i) > info.connectionStateIndex) {
    358                 info.connectionStateIndex = info.profileStates.valueAt(i);
    359             }
    360         }
    361         if (state == BluetoothProfile.STATE_CONNECTED) {
    362             info.connectedProfiles.put(profile, true);
    363         } else {
    364             info.connectedProfiles.remove(profile);
    365         }
    366     }
    367 
    368     private void handleConnectionChange() {
    369         // If we are no longer connected to the current device, see if we are connected to
    370         // something else, so we don't display a name we aren't connected to.
    371         if (mLastDevice != null &&
    372                 CONNECTION_STATES[mDeviceInfo.get(mLastDevice).connectionStateIndex]
    373                         != BluetoothProfile.STATE_CONNECTED) {
    374             // Make sure we don't keep this device while it isn't connected.
    375             mLastDevice = null;
    376             // Look for anything else connected.
    377             final int size = mDeviceInfo.size();
    378             for (int i = 0; i < size; i++) {
    379                 BluetoothDevice device = mDeviceInfo.keyAt(i);
    380                 DeviceInfo info = mDeviceInfo.valueAt(i);
    381                 if (CONNECTION_STATES[info.connectionStateIndex]
    382                         == BluetoothProfile.STATE_CONNECTED) {
    383                     mLastDevice = device;
    384                     break;
    385                 }
    386             }
    387         }
    388     }
    389 
    390     private void bindAllProfiles() {
    391         // Note: This needs to contain all of the types that can be returned by BluetoothUtil
    392         // otherwise we can't find the profiles we need when we connect/disconnect.
    393         mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
    394         mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP_SINK);
    395         mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.AVRCP_CONTROLLER);
    396         mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET);
    397         mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET_CLIENT);
    398         mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.INPUT_DEVICE);
    399         mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.MAP);
    400         mAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.PAN);
    401         // Note Health is not in this list because health devices aren't 'connected'.
    402         // If profiles are expanded to use more than just connection state and connect/disconnect
    403         // then it should be added.
    404     }
    405 
    406     private void firePairedDevicesChanged() {
    407         for (Callback cb : mCallbacks) {
    408             cb.onBluetoothPairedDevicesChanged();
    409         }
    410     }
    411 
    412     private void setAdapterState(int adapterState) {
    413         final boolean enabled = adapterState == BluetoothAdapter.STATE_ON;
    414         if (mEnabled == enabled) return;
    415         mEnabled = enabled;
    416         fireStateChange();
    417     }
    418 
    419     private void setConnecting(boolean connecting) {
    420         if (mConnecting == connecting) return;
    421         mConnecting = connecting;
    422         fireStateChange();
    423     }
    424 
    425     private void fireStateChange() {
    426         for (Callback cb : mCallbacks) {
    427             fireStateChange(cb);
    428         }
    429     }
    430 
    431     private void fireStateChange(Callback cb) {
    432         cb.onBluetoothStateChange(mEnabled, mConnecting);
    433     }
    434 
    435     private static int getProfileFromAction(String action) {
    436         if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
    437             return BluetoothProfile.A2DP;
    438         } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
    439             return BluetoothProfile.HEADSET;
    440         } else if (BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
    441             return BluetoothProfile.A2DP_SINK;
    442         } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
    443             return BluetoothProfile.HEADSET_CLIENT;
    444         } else if (BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
    445             return BluetoothProfile.INPUT_DEVICE;
    446         } else if (BluetoothMap.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
    447             return BluetoothProfile.MAP;
    448         } else if (BluetoothPan.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
    449             return BluetoothProfile.PAN;
    450         }
    451         if (DEBUG) Log.d(TAG, "Unknown action " + action);
    452         return -1;
    453     }
    454 
    455     private final ServiceListener mProfileListener = new ServiceListener() {
    456         @Override
    457         public void onServiceDisconnected(int profile) {
    458             if (DEBUG) Log.d(TAG, "Disconnected from " + BluetoothUtil.profileToString(profile));
    459             // We lost a profile, don't do any updates until it gets removed.
    460             mHandler.removeMessages(MSG_UPDATE_CONNECTION_STATES);
    461             mHandler.removeMessages(MSG_UPDATE_SINGLE_CONNECTION_STATE);
    462             mHandler.obtainMessage(MSG_REM_PROFILE, profile, 0).sendToTarget();
    463         }
    464 
    465         @Override
    466         public void onServiceConnected(int profile, BluetoothProfile proxy) {
    467             if (DEBUG) Log.d(TAG, "Connected to " + BluetoothUtil.profileToString(profile));
    468             mHandler.obtainMessage(MSG_ADD_PROFILE, profile, 0, proxy).sendToTarget();
    469         }
    470     };
    471 
    472     private final class Receiver extends BroadcastReceiver {
    473         public void register() {
    474             final IntentFilter filter = new IntentFilter();
    475             filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
    476             filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
    477             filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
    478             filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
    479             filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
    480             filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
    481             filter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
    482             filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
    483             filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
    484             filter.addAction(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
    485             filter.addAction(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
    486             mContext.registerReceiver(this, filter);
    487         }
    488 
    489         @Override
    490         public void onReceive(Context context, Intent intent) {
    491             final String action = intent.getAction();
    492             final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    493 
    494             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
    495                 setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR));
    496                 updateBondedDevices();
    497                 if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled);
    498             } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
    499                 updateInfo(device);
    500                 final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
    501                         ERROR);
    502                 mLastDevice = device;
    503                 if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED "
    504                         + connectionStateToString(state) + " " + deviceToString(device));
    505                 setConnecting(state == BluetoothAdapter.STATE_CONNECTING);
    506             } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
    507                 updateInfo(device);
    508                 mLastDevice = device;
    509             } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
    510                 if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device);
    511                 updateBondedDevices();
    512             } else {
    513                 int profile = getProfileFromAction(intent.getAction());
    514                 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
    515                 if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGE "
    516                         + BluetoothUtil.profileToString(profile)
    517                         + " " + BluetoothUtil.connectionStateToString(state));
    518                 if ((profile != -1) && (state != -1)) {
    519                     updateConnectionState(device, profile, state);
    520                 }
    521             }
    522         }
    523     }
    524 
    525     private DeviceInfo updateInfo(BluetoothDevice device) {
    526         DeviceInfo info = mDeviceInfo.get(device);
    527         info = info != null ? info : new DeviceInfo();
    528         mDeviceInfo.put(device, info);
    529         return info;
    530     }
    531 
    532     private class H extends Handler {
    533         public H(Looper l) {
    534             super(l);
    535         }
    536 
    537         public void handleMessage(Message msg) {
    538             switch (msg.what) {
    539                 case MSG_UPDATE_CONNECTION_STATES:
    540                     handleUpdateConnectionStates();
    541                     firePairedDevicesChanged();
    542                     break;
    543                 case MSG_UPDATE_SINGLE_CONNECTION_STATE:
    544                     handleUpdateConnectionState((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
    545                     handleConnectionChange();
    546                     firePairedDevicesChanged();
    547                     break;
    548                 case MSG_UPDATE_BONDED_DEVICES:
    549                     handleUpdateBondedDevices();
    550                     firePairedDevicesChanged();
    551                     break;
    552                 case MSG_ADD_PROFILE:
    553                     mProfiles.put(msg.arg1, (BluetoothProfile) msg.obj);
    554                     handleUpdateConnectionStates();
    555                     firePairedDevicesChanged();
    556                     break;
    557                 case MSG_REM_PROFILE:
    558                     mProfiles.remove(msg.arg1);
    559                     handleUpdateConnectionStates();
    560                     firePairedDevicesChanged();
    561                     break;
    562             }
    563         };
    564     };
    565 
    566     private static class DeviceInfo {
    567         int connectionStateIndex = 0;
    568         boolean bonded;  // per getBondedDevices
    569         SparseArray<Boolean> connectedProfiles = new SparseArray<>();
    570         SparseArray<Integer> profileStates = new SparseArray<>();
    571     }
    572 }
    573