1 /* 2 * Copyright 2018 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.bluetooth.hearingaid; 18 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.BluetoothHearingAid; 21 import android.bluetooth.BluetoothProfile; 22 import android.bluetooth.BluetoothUuid; 23 import android.bluetooth.IBluetoothHearingAid; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.media.AudioManager; 29 import android.os.HandlerThread; 30 import android.os.ParcelUuid; 31 import android.provider.Settings; 32 import android.support.annotation.VisibleForTesting; 33 import android.util.Log; 34 35 import com.android.bluetooth.BluetoothMetricsProto; 36 import com.android.bluetooth.Utils; 37 import com.android.bluetooth.btservice.AdapterService; 38 import com.android.bluetooth.btservice.MetricsLogger; 39 import com.android.bluetooth.btservice.ProfileService; 40 41 import java.util.ArrayList; 42 import java.util.HashMap; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.Objects; 46 import java.util.concurrent.ConcurrentHashMap; 47 48 /** 49 * Provides Bluetooth HearingAid profile, as a service in the Bluetooth application. 50 * @hide 51 */ 52 public class HearingAidService extends ProfileService { 53 private static final boolean DBG = false; 54 private static final String TAG = "HearingAidService"; 55 56 // Upper limit of all HearingAid devices: Bonded or Connected 57 private static final int MAX_HEARING_AID_STATE_MACHINES = 10; 58 private static HearingAidService sHearingAidService; 59 60 private AdapterService mAdapterService; 61 private HandlerThread mStateMachinesThread; 62 private BluetoothDevice mPreviousAudioDevice; 63 64 @VisibleForTesting 65 HearingAidNativeInterface mHearingAidNativeInterface; 66 @VisibleForTesting 67 AudioManager mAudioManager; 68 69 private final Map<BluetoothDevice, HearingAidStateMachine> mStateMachines = 70 new HashMap<>(); 71 private final Map<BluetoothDevice, Long> mDeviceHiSyncIdMap = new ConcurrentHashMap<>(); 72 private final Map<BluetoothDevice, Integer> mDeviceCapabilitiesMap = new HashMap<>(); 73 private long mActiveDeviceHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID; 74 75 private BroadcastReceiver mBondStateChangedReceiver; 76 private BroadcastReceiver mConnectionStateChangedReceiver; 77 78 @Override initBinder()79 protected IProfileServiceBinder initBinder() { 80 return new BluetoothHearingAidBinder(this); 81 } 82 83 @Override create()84 protected void create() { 85 if (DBG) { 86 Log.d(TAG, "create()"); 87 } 88 } 89 90 @Override start()91 protected boolean start() { 92 if (DBG) { 93 Log.d(TAG, "start()"); 94 } 95 if (sHearingAidService != null) { 96 throw new IllegalStateException("start() called twice"); 97 } 98 99 // Get AdapterService, HearingAidNativeInterface, AudioManager. 100 // None of them can be null. 101 mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), 102 "AdapterService cannot be null when HearingAidService starts"); 103 mHearingAidNativeInterface = Objects.requireNonNull(HearingAidNativeInterface.getInstance(), 104 "HearingAidNativeInterface cannot be null when HearingAidService starts"); 105 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 106 Objects.requireNonNull(mAudioManager, 107 "AudioManager cannot be null when HearingAidService starts"); 108 109 // Start handler thread for state machines 110 mStateMachines.clear(); 111 mStateMachinesThread = new HandlerThread("HearingAidService.StateMachines"); 112 mStateMachinesThread.start(); 113 114 // Clear HiSyncId map and capabilities map 115 mDeviceHiSyncIdMap.clear(); 116 mDeviceCapabilitiesMap.clear(); 117 118 // Setup broadcast receivers 119 IntentFilter filter = new IntentFilter(); 120 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 121 mBondStateChangedReceiver = new BondStateChangedReceiver(); 122 registerReceiver(mBondStateChangedReceiver, filter); 123 filter = new IntentFilter(); 124 filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); 125 mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver(); 126 registerReceiver(mConnectionStateChangedReceiver, filter); 127 128 // Mark service as started 129 setHearingAidService(this); 130 131 // Initialize native interface 132 mHearingAidNativeInterface.init(); 133 134 return true; 135 } 136 137 @Override stop()138 protected boolean stop() { 139 if (DBG) { 140 Log.d(TAG, "stop()"); 141 } 142 if (sHearingAidService == null) { 143 Log.w(TAG, "stop() called before start()"); 144 return true; 145 } 146 147 // Cleanup native interface 148 mHearingAidNativeInterface.cleanup(); 149 mHearingAidNativeInterface = null; 150 151 // Mark service as stopped 152 setHearingAidService(null); 153 154 // Unregister broadcast receivers 155 unregisterReceiver(mBondStateChangedReceiver); 156 mBondStateChangedReceiver = null; 157 unregisterReceiver(mConnectionStateChangedReceiver); 158 mConnectionStateChangedReceiver = null; 159 160 // Destroy state machines and stop handler thread 161 synchronized (mStateMachines) { 162 for (HearingAidStateMachine sm : mStateMachines.values()) { 163 sm.doQuit(); 164 sm.cleanup(); 165 } 166 mStateMachines.clear(); 167 } 168 169 // Clear HiSyncId map and capabilities map 170 mDeviceHiSyncIdMap.clear(); 171 mDeviceCapabilitiesMap.clear(); 172 173 if (mStateMachinesThread != null) { 174 mStateMachinesThread.quitSafely(); 175 mStateMachinesThread = null; 176 } 177 178 // Clear AdapterService, HearingAidNativeInterface 179 mAudioManager = null; 180 mHearingAidNativeInterface = null; 181 mAdapterService = null; 182 183 return true; 184 } 185 186 @Override cleanup()187 protected void cleanup() { 188 if (DBG) { 189 Log.d(TAG, "cleanup()"); 190 } 191 } 192 193 /** 194 * Get the HearingAidService instance 195 * @return HearingAidService instance 196 */ getHearingAidService()197 public static synchronized HearingAidService getHearingAidService() { 198 if (sHearingAidService == null) { 199 Log.w(TAG, "getHearingAidService(): service is NULL"); 200 return null; 201 } 202 203 if (!sHearingAidService.isAvailable()) { 204 Log.w(TAG, "getHearingAidService(): service is not available"); 205 return null; 206 } 207 return sHearingAidService; 208 } 209 setHearingAidService(HearingAidService instance)210 private static synchronized void setHearingAidService(HearingAidService instance) { 211 if (DBG) { 212 Log.d(TAG, "setHearingAidService(): set to: " + instance); 213 } 214 sHearingAidService = instance; 215 } 216 connect(BluetoothDevice device)217 boolean connect(BluetoothDevice device) { 218 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 219 if (DBG) { 220 Log.d(TAG, "connect(): " + device); 221 } 222 if (device == null) { 223 return false; 224 } 225 226 if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) { 227 return false; 228 } 229 ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device); 230 if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.HearingAid)) { 231 Log.e(TAG, "Cannot connect to " + device + " : Remote does not have Hearing Aid UUID"); 232 return false; 233 } 234 235 long hiSyncId = mDeviceHiSyncIdMap.getOrDefault(device, 236 BluetoothHearingAid.HI_SYNC_ID_INVALID); 237 238 if (hiSyncId != mActiveDeviceHiSyncId 239 && hiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID 240 && mActiveDeviceHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) { 241 for (BluetoothDevice connectedDevice : getConnectedDevices()) { 242 disconnect(connectedDevice); 243 } 244 } 245 246 synchronized (mStateMachines) { 247 HearingAidStateMachine smConnect = getOrCreateStateMachine(device); 248 if (smConnect == null) { 249 Log.e(TAG, "Cannot connect to " + device + " : no state machine"); 250 } 251 smConnect.sendMessage(HearingAidStateMachine.CONNECT); 252 } 253 254 for (BluetoothDevice storedDevice : mDeviceHiSyncIdMap.keySet()) { 255 if (device.equals(storedDevice)) { 256 continue; 257 } 258 if (mDeviceHiSyncIdMap.getOrDefault(storedDevice, 259 BluetoothHearingAid.HI_SYNC_ID_INVALID) == hiSyncId) { 260 synchronized (mStateMachines) { 261 HearingAidStateMachine sm = getOrCreateStateMachine(storedDevice); 262 if (sm == null) { 263 Log.e(TAG, "Ignored connect request for " + device + " : no state machine"); 264 continue; 265 } 266 sm.sendMessage(HearingAidStateMachine.CONNECT); 267 } 268 if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID 269 && !device.equals(storedDevice)) { 270 break; 271 } 272 } 273 } 274 return true; 275 } 276 disconnect(BluetoothDevice device)277 boolean disconnect(BluetoothDevice device) { 278 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 279 if (DBG) { 280 Log.d(TAG, "disconnect(): " + device); 281 } 282 if (device == null) { 283 return false; 284 } 285 long hiSyncId = mDeviceHiSyncIdMap.getOrDefault(device, 286 BluetoothHearingAid.HI_SYNC_ID_INVALID); 287 288 for (BluetoothDevice storedDevice : mDeviceHiSyncIdMap.keySet()) { 289 if (mDeviceHiSyncIdMap.getOrDefault(storedDevice, 290 BluetoothHearingAid.HI_SYNC_ID_INVALID) == hiSyncId) { 291 synchronized (mStateMachines) { 292 HearingAidStateMachine sm = mStateMachines.get(storedDevice); 293 if (sm == null) { 294 Log.e(TAG, "Ignored disconnect request for " + device 295 + " : no state machine"); 296 continue; 297 } 298 sm.sendMessage(HearingAidStateMachine.DISCONNECT); 299 } 300 if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID 301 && !device.equals(storedDevice)) { 302 break; 303 } 304 } 305 } 306 return true; 307 } 308 getConnectedDevices()309 List<BluetoothDevice> getConnectedDevices() { 310 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 311 synchronized (mStateMachines) { 312 List<BluetoothDevice> devices = new ArrayList<>(); 313 for (HearingAidStateMachine sm : mStateMachines.values()) { 314 if (sm.isConnected()) { 315 devices.add(sm.getDevice()); 316 } 317 } 318 return devices; 319 } 320 } 321 322 /** 323 * Check whether can connect to a peer device. 324 * The check considers a number of factors during the evaluation. 325 * 326 * @param device the peer device to connect to 327 * @return true if connection is allowed, otherwise false 328 */ 329 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) okToConnect(BluetoothDevice device)330 public boolean okToConnect(BluetoothDevice device) { 331 // Check if this is an incoming connection in Quiet mode. 332 if (mAdapterService.isQuietModeEnabled()) { 333 Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled"); 334 return false; 335 } 336 // Check priority and accept or reject the connection. 337 int priority = getPriority(device); 338 int bondState = mAdapterService.getBondState(device); 339 // Allow this connection only if the device is bonded. Any attempt to connect while 340 // bonding would potentially lead to an unauthorized connection. 341 if (bondState != BluetoothDevice.BOND_BONDED) { 342 Log.w(TAG, "okToConnect: return false, bondState=" + bondState); 343 return false; 344 } else if (priority != BluetoothProfile.PRIORITY_UNDEFINED 345 && priority != BluetoothProfile.PRIORITY_ON 346 && priority != BluetoothProfile.PRIORITY_AUTO_CONNECT) { 347 // Otherwise, reject the connection if priority is not valid. 348 Log.w(TAG, "okToConnect: return false, priority=" + priority); 349 return false; 350 } 351 return true; 352 } 353 getDevicesMatchingConnectionStates(int[] states)354 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 355 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 356 ArrayList<BluetoothDevice> devices = new ArrayList<>(); 357 if (states == null) { 358 return devices; 359 } 360 final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 361 if (bondedDevices == null) { 362 return devices; 363 } 364 synchronized (mStateMachines) { 365 for (BluetoothDevice device : bondedDevices) { 366 final ParcelUuid[] featureUuids = device.getUuids(); 367 if (!BluetoothUuid.isUuidPresent(featureUuids, BluetoothUuid.HearingAid)) { 368 continue; 369 } 370 int connectionState = BluetoothProfile.STATE_DISCONNECTED; 371 HearingAidStateMachine sm = mStateMachines.get(device); 372 if (sm != null) { 373 connectionState = sm.getConnectionState(); 374 } 375 for (int state : states) { 376 if (connectionState == state) { 377 devices.add(device); 378 break; 379 } 380 } 381 } 382 return devices; 383 } 384 } 385 386 /** 387 * Get the list of devices that have state machines. 388 * 389 * @return the list of devices that have state machines 390 */ 391 @VisibleForTesting getDevices()392 List<BluetoothDevice> getDevices() { 393 List<BluetoothDevice> devices = new ArrayList<>(); 394 synchronized (mStateMachines) { 395 for (HearingAidStateMachine sm : mStateMachines.values()) { 396 devices.add(sm.getDevice()); 397 } 398 return devices; 399 } 400 } 401 getConnectionState(BluetoothDevice device)402 int getConnectionState(BluetoothDevice device) { 403 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 404 synchronized (mStateMachines) { 405 HearingAidStateMachine sm = mStateMachines.get(device); 406 if (sm == null) { 407 return BluetoothProfile.STATE_DISCONNECTED; 408 } 409 return sm.getConnectionState(); 410 } 411 } 412 413 /** 414 * Set the priority of the Hearing Aid profile. 415 * 416 * @param device the remote device 417 * @param priority the priority of the profile 418 * @return true on success, otherwise false 419 */ setPriority(BluetoothDevice device, int priority)420 public boolean setPriority(BluetoothDevice device, int priority) { 421 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 422 Settings.Global.putInt(getContentResolver(), 423 Settings.Global.getBluetoothHearingAidPriorityKey(device.getAddress()), priority); 424 if (DBG) { 425 Log.d(TAG, "Saved priority " + device + " = " + priority); 426 } 427 return true; 428 } 429 getPriority(BluetoothDevice device)430 public int getPriority(BluetoothDevice device) { 431 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 432 int priority = Settings.Global.getInt(getContentResolver(), 433 Settings.Global.getBluetoothHearingAidPriorityKey(device.getAddress()), 434 BluetoothProfile.PRIORITY_UNDEFINED); 435 return priority; 436 } 437 setVolume(int volume)438 void setVolume(int volume) { 439 mHearingAidNativeInterface.setVolume(volume); 440 } 441 getHiSyncId(BluetoothDevice device)442 long getHiSyncId(BluetoothDevice device) { 443 if (device == null) { 444 return BluetoothHearingAid.HI_SYNC_ID_INVALID; 445 } 446 return mDeviceHiSyncIdMap.getOrDefault(device, BluetoothHearingAid.HI_SYNC_ID_INVALID); 447 } 448 getCapabilities(BluetoothDevice device)449 int getCapabilities(BluetoothDevice device) { 450 return mDeviceCapabilitiesMap.getOrDefault(device, -1); 451 } 452 453 /** 454 * Set the active device. 455 * @param device the new active device 456 * @return true on success, otherwise false 457 */ setActiveDevice(BluetoothDevice device)458 public boolean setActiveDevice(BluetoothDevice device) { 459 enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); 460 if (DBG) { 461 Log.d(TAG, "setActiveDevice:" + device); 462 } 463 synchronized (mStateMachines) { 464 if (device == null) { 465 if (mActiveDeviceHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) { 466 reportActiveDevice(null); 467 mActiveDeviceHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID; 468 } 469 return true; 470 } 471 if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { 472 Log.e(TAG, "setActiveDevice(" + device + "): failed because device not connected"); 473 return false; 474 } 475 Long deviceHiSyncId = mDeviceHiSyncIdMap.getOrDefault(device, 476 BluetoothHearingAid.HI_SYNC_ID_INVALID); 477 if (deviceHiSyncId != mActiveDeviceHiSyncId) { 478 mActiveDeviceHiSyncId = deviceHiSyncId; 479 reportActiveDevice(device); 480 } 481 } 482 return true; 483 } 484 485 /** 486 * Get the connected physical Hearing Aid devices that are active 487 * 488 * @return the list of active devices. The first element is the left active 489 * device; the second element is the right active device. If either or both side 490 * is not active, it will be null on that position 491 */ getActiveDevices()492 List<BluetoothDevice> getActiveDevices() { 493 if (DBG) { 494 Log.d(TAG, "getActiveDevices"); 495 } 496 ArrayList<BluetoothDevice> activeDevices = new ArrayList<>(); 497 activeDevices.add(null); 498 activeDevices.add(null); 499 synchronized (mStateMachines) { 500 if (mActiveDeviceHiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) { 501 return activeDevices; 502 } 503 for (BluetoothDevice device : mDeviceHiSyncIdMap.keySet()) { 504 if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { 505 continue; 506 } 507 if (mDeviceHiSyncIdMap.get(device) == mActiveDeviceHiSyncId) { 508 int deviceSide = getCapabilities(device) & 1; 509 if (deviceSide == BluetoothHearingAid.SIDE_RIGHT) { 510 activeDevices.set(1, device); 511 } else { 512 activeDevices.set(0, device); 513 } 514 } 515 } 516 } 517 return activeDevices; 518 } 519 messageFromNative(HearingAidStackEvent stackEvent)520 void messageFromNative(HearingAidStackEvent stackEvent) { 521 Objects.requireNonNull(stackEvent.device, 522 "Device should never be null, event: " + stackEvent); 523 524 if (stackEvent.type == HearingAidStackEvent.EVENT_TYPE_DEVICE_AVAILABLE) { 525 BluetoothDevice device = stackEvent.device; 526 int capabilities = stackEvent.valueInt1; 527 long hiSyncId = stackEvent.valueLong2; 528 if (DBG) { 529 Log.d(TAG, "Device available: device=" + device + " capabilities=" 530 + capabilities + " hiSyncId=" + hiSyncId); 531 } 532 mDeviceCapabilitiesMap.put(device, capabilities); 533 mDeviceHiSyncIdMap.put(device, hiSyncId); 534 return; 535 } 536 537 synchronized (mStateMachines) { 538 BluetoothDevice device = stackEvent.device; 539 HearingAidStateMachine sm = mStateMachines.get(device); 540 if (sm == null) { 541 if (stackEvent.type == HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { 542 switch (stackEvent.valueInt1) { 543 case HearingAidStackEvent.CONNECTION_STATE_CONNECTED: 544 case HearingAidStackEvent.CONNECTION_STATE_CONNECTING: 545 sm = getOrCreateStateMachine(device); 546 break; 547 default: 548 break; 549 } 550 } 551 } 552 if (sm == null) { 553 Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); 554 return; 555 } 556 sm.sendMessage(HearingAidStateMachine.STACK_EVENT, stackEvent); 557 } 558 } 559 getOrCreateStateMachine(BluetoothDevice device)560 private HearingAidStateMachine getOrCreateStateMachine(BluetoothDevice device) { 561 if (device == null) { 562 Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); 563 return null; 564 } 565 synchronized (mStateMachines) { 566 HearingAidStateMachine sm = mStateMachines.get(device); 567 if (sm != null) { 568 return sm; 569 } 570 // Limit the maximum number of state machines to avoid DoS attack 571 if (mStateMachines.size() >= MAX_HEARING_AID_STATE_MACHINES) { 572 Log.e(TAG, "Maximum number of HearingAid state machines reached: " 573 + MAX_HEARING_AID_STATE_MACHINES); 574 return null; 575 } 576 if (DBG) { 577 Log.d(TAG, "Creating a new state machine for " + device); 578 } 579 sm = HearingAidStateMachine.make(device, this, 580 mHearingAidNativeInterface, mStateMachinesThread.getLooper()); 581 mStateMachines.put(device, sm); 582 return sm; 583 } 584 } 585 586 /** 587 * Report the active device change to the active device manager and the media framework. 588 * @param device the new active device; or null if no active device 589 */ reportActiveDevice(BluetoothDevice device)590 private void reportActiveDevice(BluetoothDevice device) { 591 if (DBG) { 592 Log.d(TAG, "reportActiveDevice(" + device + ")"); 593 } 594 595 Intent intent = new Intent(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); 596 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 597 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 598 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 599 sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 600 601 if (device == null) { 602 if (DBG) { 603 Log.d(TAG, "Set Hearing Aid audio to disconnected"); 604 } 605 mAudioManager.setHearingAidDeviceConnectionState(mPreviousAudioDevice, 606 BluetoothProfile.STATE_DISCONNECTED); 607 mPreviousAudioDevice = null; 608 } else { 609 if (DBG) { 610 Log.d(TAG, "Set Hearing Aid audio to connected"); 611 } 612 if (mPreviousAudioDevice != null) { 613 mAudioManager.setHearingAidDeviceConnectionState(mPreviousAudioDevice, 614 BluetoothProfile.STATE_DISCONNECTED); 615 } 616 mAudioManager.setHearingAidDeviceConnectionState(device, 617 BluetoothProfile.STATE_CONNECTED); 618 mPreviousAudioDevice = device; 619 } 620 } 621 622 // Remove state machine if the bonding for a device is removed 623 private class BondStateChangedReceiver extends BroadcastReceiver { 624 @Override onReceive(Context context, Intent intent)625 public void onReceive(Context context, Intent intent) { 626 if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { 627 return; 628 } 629 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 630 BluetoothDevice.ERROR); 631 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 632 Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); 633 bondStateChanged(device, state); 634 } 635 } 636 637 /** 638 * Process a change in the bonding state for a device. 639 * 640 * @param device the device whose bonding state has changed 641 * @param bondState the new bond state for the device. Possible values are: 642 * {@link BluetoothDevice#BOND_NONE}, 643 * {@link BluetoothDevice#BOND_BONDING}, 644 * {@link BluetoothDevice#BOND_BONDED}. 645 */ 646 @VisibleForTesting bondStateChanged(BluetoothDevice device, int bondState)647 void bondStateChanged(BluetoothDevice device, int bondState) { 648 if (DBG) { 649 Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); 650 } 651 // Remove state machine if the bonding for a device is removed 652 if (bondState != BluetoothDevice.BOND_NONE) { 653 return; 654 } 655 synchronized (mStateMachines) { 656 HearingAidStateMachine sm = mStateMachines.get(device); 657 if (sm == null) { 658 return; 659 } 660 if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { 661 return; 662 } 663 removeStateMachine(device); 664 } 665 } 666 removeStateMachine(BluetoothDevice device)667 private void removeStateMachine(BluetoothDevice device) { 668 synchronized (mStateMachines) { 669 HearingAidStateMachine sm = mStateMachines.get(device); 670 if (sm == null) { 671 Log.w(TAG, "removeStateMachine: device " + device 672 + " does not have a state machine"); 673 return; 674 } 675 Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); 676 sm.doQuit(); 677 sm.cleanup(); 678 mStateMachines.remove(device); 679 } 680 } 681 getConnectedPeerDevices(long hiSyncId)682 private List<BluetoothDevice> getConnectedPeerDevices(long hiSyncId) { 683 List<BluetoothDevice> result = new ArrayList<>(); 684 for (BluetoothDevice peerDevice : getConnectedDevices()) { 685 if (getHiSyncId(peerDevice) == hiSyncId) { 686 result.add(peerDevice); 687 } 688 } 689 return result; 690 } 691 692 @VisibleForTesting connectionStateChanged(BluetoothDevice device, int fromState, int toState)693 synchronized void connectionStateChanged(BluetoothDevice device, int fromState, 694 int toState) { 695 if ((device == null) || (fromState == toState)) { 696 Log.e(TAG, "connectionStateChanged: unexpected invocation. device=" + device 697 + " fromState=" + fromState + " toState=" + toState); 698 return; 699 } 700 if (toState == BluetoothProfile.STATE_CONNECTED) { 701 long myHiSyncId = getHiSyncId(device); 702 if (myHiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID 703 || getConnectedPeerDevices(myHiSyncId).size() == 1) { 704 // Log hearing aid connection event if we are the first device in a set 705 // Or when the hiSyncId has not been found 706 MetricsLogger.logProfileConnectionEvent( 707 BluetoothMetricsProto.ProfileId.HEARING_AID); 708 } 709 setActiveDevice(device); 710 } 711 if (fromState == BluetoothProfile.STATE_CONNECTED && getConnectedDevices().isEmpty()) { 712 setActiveDevice(null); 713 } 714 // Check if the device is disconnected - if unbond, remove the state machine 715 if (toState == BluetoothProfile.STATE_DISCONNECTED) { 716 int bondState = mAdapterService.getBondState(device); 717 if (bondState == BluetoothDevice.BOND_NONE) { 718 if (DBG) { 719 Log.d(TAG, device + " is unbond. Remove state machine"); 720 } 721 removeStateMachine(device); 722 } 723 } 724 } 725 726 private class ConnectionStateChangedReceiver extends BroadcastReceiver { 727 @Override onReceive(Context context, Intent intent)728 public void onReceive(Context context, Intent intent) { 729 if (!BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 730 return; 731 } 732 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 733 int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 734 int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 735 connectionStateChanged(device, fromState, toState); 736 } 737 } 738 739 /** 740 * Binder object: must be a static class or memory leak may occur 741 */ 742 @VisibleForTesting 743 static class BluetoothHearingAidBinder extends IBluetoothHearingAid.Stub 744 implements IProfileServiceBinder { 745 private HearingAidService mService; 746 getService()747 private HearingAidService getService() { 748 if (!Utils.checkCaller()) { 749 Log.w(TAG, "HearingAid call not allowed for non-active user"); 750 return null; 751 } 752 753 if (mService != null && mService.isAvailable()) { 754 return mService; 755 } 756 return null; 757 } 758 BluetoothHearingAidBinder(HearingAidService svc)759 BluetoothHearingAidBinder(HearingAidService svc) { 760 mService = svc; 761 } 762 763 @Override cleanup()764 public void cleanup() { 765 mService = null; 766 } 767 768 @Override connect(BluetoothDevice device)769 public boolean connect(BluetoothDevice device) { 770 HearingAidService service = getService(); 771 if (service == null) { 772 return false; 773 } 774 return service.connect(device); 775 } 776 777 @Override disconnect(BluetoothDevice device)778 public boolean disconnect(BluetoothDevice device) { 779 HearingAidService service = getService(); 780 if (service == null) { 781 return false; 782 } 783 return service.disconnect(device); 784 } 785 786 @Override getConnectedDevices()787 public List<BluetoothDevice> getConnectedDevices() { 788 HearingAidService service = getService(); 789 if (service == null) { 790 return new ArrayList<>(); 791 } 792 return service.getConnectedDevices(); 793 } 794 795 @Override getDevicesMatchingConnectionStates(int[] states)796 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 797 HearingAidService service = getService(); 798 if (service == null) { 799 return new ArrayList<>(); 800 } 801 return service.getDevicesMatchingConnectionStates(states); 802 } 803 804 @Override getConnectionState(BluetoothDevice device)805 public int getConnectionState(BluetoothDevice device) { 806 HearingAidService service = getService(); 807 if (service == null) { 808 return BluetoothProfile.STATE_DISCONNECTED; 809 } 810 return service.getConnectionState(device); 811 } 812 813 @Override setActiveDevice(BluetoothDevice device)814 public boolean setActiveDevice(BluetoothDevice device) { 815 HearingAidService service = getService(); 816 if (service == null) { 817 return false; 818 } 819 return service.setActiveDevice(device); 820 } 821 822 @Override getActiveDevices()823 public List<BluetoothDevice> getActiveDevices() { 824 HearingAidService service = getService(); 825 if (service == null) { 826 return new ArrayList<>(); 827 } 828 return service.getActiveDevices(); 829 } 830 831 @Override setPriority(BluetoothDevice device, int priority)832 public boolean setPriority(BluetoothDevice device, int priority) { 833 HearingAidService service = getService(); 834 if (service == null) { 835 return false; 836 } 837 return service.setPriority(device, priority); 838 } 839 840 @Override getPriority(BluetoothDevice device)841 public int getPriority(BluetoothDevice device) { 842 HearingAidService service = getService(); 843 if (service == null) { 844 return BluetoothProfile.PRIORITY_UNDEFINED; 845 } 846 return service.getPriority(device); 847 } 848 849 @Override setVolume(int volume)850 public void setVolume(int volume) { 851 HearingAidService service = getService(); 852 if (service == null) { 853 return; 854 } 855 service.setVolume(volume); 856 } 857 858 @Override adjustVolume(int direction)859 public void adjustVolume(int direction) { 860 // TODO: Remove me? 861 } 862 863 @Override getVolume()864 public int getVolume() { 865 // TODO: Remove me? 866 return 0; 867 } 868 869 @Override getHiSyncId(BluetoothDevice device)870 public long getHiSyncId(BluetoothDevice device) { 871 HearingAidService service = getService(); 872 if (service == null) { 873 return BluetoothHearingAid.HI_SYNC_ID_INVALID; 874 } 875 return service.getHiSyncId(device); 876 } 877 878 @Override getDeviceSide(BluetoothDevice device)879 public int getDeviceSide(BluetoothDevice device) { 880 HearingAidService service = getService(); 881 if (service == null) { 882 return BluetoothHearingAid.SIDE_RIGHT; 883 } 884 return service.getCapabilities(device) & 1; 885 } 886 887 @Override getDeviceMode(BluetoothDevice device)888 public int getDeviceMode(BluetoothDevice device) { 889 HearingAidService service = getService(); 890 if (service == null) { 891 return BluetoothHearingAid.MODE_BINAURAL; 892 } 893 return service.getCapabilities(device) >> 1 & 1; 894 } 895 } 896 897 @Override dump(StringBuilder sb)898 public void dump(StringBuilder sb) { 899 super.dump(sb); 900 for (HearingAidStateMachine sm : mStateMachines.values()) { 901 sm.dump(sb); 902 } 903 } 904 } 905