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