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