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 static android.Manifest.permission.BLUETOOTH_CONNECT; 20 21 import static com.android.bluetooth.Utils.callerIsSystemOrActiveOrManagedUser; 22 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; 23 24 import android.annotation.RequiresPermission; 25 import android.bluetooth.BluetoothDevice; 26 import android.bluetooth.BluetoothHearingAid; 27 import android.bluetooth.BluetoothHearingAid.AdvertisementServiceData; 28 import android.bluetooth.BluetoothProfile; 29 import android.bluetooth.BluetoothUuid; 30 import android.bluetooth.IBluetoothHearingAid; 31 import android.content.AttributionSource; 32 import android.content.BroadcastReceiver; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.IntentFilter; 36 import android.media.AudioDeviceCallback; 37 import android.media.AudioDeviceInfo; 38 import android.media.AudioManager; 39 import android.media.BluetoothProfileConnectionInfo; 40 import android.os.Handler; 41 import android.os.HandlerThread; 42 import android.os.Looper; 43 import android.os.ParcelUuid; 44 import android.sysprop.BluetoothProperties; 45 import android.util.Log; 46 47 import com.android.bluetooth.BluetoothMetricsProto; 48 import com.android.bluetooth.BluetoothStatsLog; 49 import com.android.bluetooth.Utils; 50 import com.android.bluetooth.btservice.AdapterService; 51 import com.android.bluetooth.btservice.MetricsLogger; 52 import com.android.bluetooth.btservice.ProfileService; 53 import com.android.bluetooth.btservice.ServiceFactory; 54 import com.android.bluetooth.btservice.storage.DatabaseManager; 55 import com.android.internal.annotations.VisibleForTesting; 56 import com.android.modules.utils.SynchronousResultReceiver; 57 58 import java.util.ArrayList; 59 import java.util.HashMap; 60 import java.util.List; 61 import java.util.Map; 62 import java.util.Objects; 63 import java.util.concurrent.ConcurrentHashMap; 64 65 /** 66 * Provides Bluetooth HearingAid profile, as a service in the Bluetooth application. 67 * @hide 68 */ 69 public class HearingAidService extends ProfileService { 70 private static final boolean DBG = true; 71 private static final String TAG = "HearingAidService"; 72 73 // Timeout for state machine thread join, to prevent potential ANR. 74 private static final int SM_THREAD_JOIN_TIMEOUT_MS = 1000; 75 76 // Upper limit of all HearingAid devices: Bonded or Connected 77 private static final int MAX_HEARING_AID_STATE_MACHINES = 10; 78 private static HearingAidService sHearingAidService; 79 80 private AdapterService mAdapterService; 81 private DatabaseManager mDatabaseManager; 82 private HandlerThread mStateMachinesThread; 83 private BluetoothDevice mActiveDevice; 84 85 @VisibleForTesting 86 HearingAidNativeInterface mHearingAidNativeInterface; 87 @VisibleForTesting 88 AudioManager mAudioManager; 89 90 private final Map<BluetoothDevice, HearingAidStateMachine> mStateMachines = 91 new HashMap<>(); 92 private final Map<BluetoothDevice, Long> mDeviceHiSyncIdMap = new ConcurrentHashMap<>(); 93 private final Map<BluetoothDevice, Integer> mDeviceCapabilitiesMap = new HashMap<>(); 94 private final Map<Long, Boolean> mHiSyncIdConnectedMap = new HashMap<>(); 95 private long mActiveDeviceHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID; 96 97 private BroadcastReceiver mBondStateChangedReceiver; 98 private Handler mHandler = new Handler(Looper.getMainLooper()); 99 private final AudioManagerOnAudioDevicesAddedCallback mAudioManagerOnAudioDevicesAddedCallback = 100 new AudioManagerOnAudioDevicesAddedCallback(); 101 private final AudioManagerOnAudioDevicesRemovedCallback 102 mAudioManagerOnAudioDevicesRemovedCallback = 103 new AudioManagerOnAudioDevicesRemovedCallback(); 104 105 private final ServiceFactory mFactory = new ServiceFactory(); 106 isEnabled()107 public static boolean isEnabled() { 108 return BluetoothProperties.isProfileAshaCentralEnabled().orElse(true); 109 } 110 111 @Override initBinder()112 protected IProfileServiceBinder initBinder() { 113 return new BluetoothHearingAidBinder(this); 114 } 115 116 @Override create()117 protected void create() { 118 if (DBG) { 119 Log.d(TAG, "create()"); 120 } 121 } 122 123 @Override start()124 protected boolean start() { 125 if (DBG) { 126 Log.d(TAG, "start()"); 127 } 128 if (sHearingAidService != null) { 129 throw new IllegalStateException("start() called twice"); 130 } 131 132 mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), 133 "AdapterService cannot be null when HearingAidService starts"); 134 mHearingAidNativeInterface = Objects.requireNonNull(HearingAidNativeInterface.getInstance(), 135 "HearingAidNativeInterface cannot be null when HearingAidService starts"); 136 mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(), 137 "DatabaseManager cannot be null when HearingAidService starts"); 138 mAudioManager = getSystemService(AudioManager.class); 139 Objects.requireNonNull(mAudioManager, 140 "AudioManager cannot be null when HearingAidService starts"); 141 142 // Start handler thread for state machines 143 mStateMachines.clear(); 144 mStateMachinesThread = new HandlerThread("HearingAidService.StateMachines"); 145 mStateMachinesThread.start(); 146 147 // Clear HiSyncId map, capabilities map and HiSyncId Connected map 148 mDeviceHiSyncIdMap.clear(); 149 mDeviceCapabilitiesMap.clear(); 150 mHiSyncIdConnectedMap.clear(); 151 152 // Setup broadcast receivers 153 IntentFilter filter = new IntentFilter(); 154 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 155 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 156 mBondStateChangedReceiver = new BondStateChangedReceiver(); 157 registerReceiver(mBondStateChangedReceiver, filter); 158 filter = new IntentFilter(); 159 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 160 filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); 161 162 // Mark service as started 163 setHearingAidService(this); 164 165 // Initialize native interface 166 mHearingAidNativeInterface.init(); 167 mAdapterService.notifyActivityAttributionInfo(getAttributionSource(), 168 AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS); 169 170 return true; 171 } 172 173 @Override stop()174 protected boolean stop() { 175 if (DBG) { 176 Log.d(TAG, "stop()"); 177 } 178 if (sHearingAidService == null) { 179 Log.w(TAG, "stop() called before start()"); 180 return true; 181 } 182 183 mAdapterService.notifyActivityAttributionInfo(getAttributionSource(), 184 AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS); 185 // Cleanup native interface 186 mHearingAidNativeInterface.cleanup(); 187 mHearingAidNativeInterface = null; 188 189 // Mark service as stopped 190 setHearingAidService(null); 191 192 // Unregister broadcast receivers 193 unregisterReceiver(mBondStateChangedReceiver); 194 mBondStateChangedReceiver = null; 195 196 // Destroy state machines and stop handler thread 197 synchronized (mStateMachines) { 198 for (HearingAidStateMachine sm : mStateMachines.values()) { 199 sm.doQuit(); 200 sm.cleanup(); 201 } 202 mStateMachines.clear(); 203 } 204 205 // Clear HiSyncId map, capabilities map and HiSyncId Connected map 206 mDeviceHiSyncIdMap.clear(); 207 mDeviceCapabilitiesMap.clear(); 208 mHiSyncIdConnectedMap.clear(); 209 210 if (mStateMachinesThread != null) { 211 try { 212 mStateMachinesThread.quitSafely(); 213 mStateMachinesThread.join(SM_THREAD_JOIN_TIMEOUT_MS); 214 mStateMachinesThread = null; 215 } catch (InterruptedException e) { 216 // Do not rethrow as we are shutting down anyway 217 } 218 } 219 220 mAudioManager.unregisterAudioDeviceCallback(mAudioManagerOnAudioDevicesAddedCallback); 221 mAudioManager.unregisterAudioDeviceCallback(mAudioManagerOnAudioDevicesRemovedCallback); 222 223 // Clear AdapterService, HearingAidNativeInterface 224 mAudioManager = null; 225 mHearingAidNativeInterface = null; 226 mAdapterService = null; 227 228 return true; 229 } 230 231 @Override cleanup()232 protected void cleanup() { 233 if (DBG) { 234 Log.d(TAG, "cleanup()"); 235 } 236 } 237 238 /** 239 * Get the HearingAidService instance 240 * @return HearingAidService instance 241 */ getHearingAidService()242 public static synchronized HearingAidService getHearingAidService() { 243 if (sHearingAidService == null) { 244 Log.w(TAG, "getHearingAidService(): service is NULL"); 245 return null; 246 } 247 248 if (!sHearingAidService.isAvailable()) { 249 Log.w(TAG, "getHearingAidService(): service is not available"); 250 return null; 251 } 252 return sHearingAidService; 253 } 254 255 @VisibleForTesting setHearingAidService(HearingAidService instance)256 static synchronized void setHearingAidService(HearingAidService instance) { 257 if (DBG) { 258 Log.d(TAG, "setHearingAidService(): set to: " + instance); 259 } 260 sHearingAidService = instance; 261 } 262 263 /** 264 * Connects the hearing aid profile to the passed in device 265 * 266 * @param device is the device with which we will connect the hearing aid profile 267 * @return true if hearing aid profile successfully connected, false otherwise 268 */ 269 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) connect(BluetoothDevice device)270 public boolean connect(BluetoothDevice device) { 271 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 272 "Need BLUETOOTH_PRIVILEGED permission"); 273 if (DBG) { 274 Log.d(TAG, "connect(): " + device); 275 } 276 if (device == null) { 277 return false; 278 } 279 280 if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 281 return false; 282 } 283 ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device); 284 if (!Utils.arrayContains(featureUuids, BluetoothUuid.HEARING_AID)) { 285 Log.e(TAG, "Cannot connect to " + device + " : Remote does not have Hearing Aid UUID"); 286 return false; 287 } 288 289 long hiSyncId = mDeviceHiSyncIdMap.getOrDefault(device, 290 BluetoothHearingAid.HI_SYNC_ID_INVALID); 291 292 if (hiSyncId != mActiveDeviceHiSyncId 293 && hiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID 294 && mActiveDeviceHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) { 295 for (BluetoothDevice connectedDevice : getConnectedDevices()) { 296 disconnect(connectedDevice); 297 } 298 } 299 300 synchronized (mStateMachines) { 301 HearingAidStateMachine smConnect = getOrCreateStateMachine(device); 302 if (smConnect == null) { 303 Log.e(TAG, "Cannot connect to " + device + " : no state machine"); 304 } 305 smConnect.sendMessage(HearingAidStateMachine.CONNECT); 306 } 307 308 for (BluetoothDevice storedDevice : mDeviceHiSyncIdMap.keySet()) { 309 if (device.equals(storedDevice)) { 310 continue; 311 } 312 if (mDeviceHiSyncIdMap.getOrDefault(storedDevice, 313 BluetoothHearingAid.HI_SYNC_ID_INVALID) == hiSyncId) { 314 synchronized (mStateMachines) { 315 HearingAidStateMachine sm = getOrCreateStateMachine(storedDevice); 316 if (sm == null) { 317 Log.e(TAG, "Ignored connect request for " + device + " : no state machine"); 318 continue; 319 } 320 sm.sendMessage(HearingAidStateMachine.CONNECT); 321 } 322 if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID 323 && !device.equals(storedDevice)) { 324 break; 325 } 326 } 327 } 328 return true; 329 } 330 331 /** 332 * Disconnects hearing aid profile for the passed in device 333 * 334 * @param device is the device with which we want to disconnected the hearing aid profile 335 * @return true if hearing aid profile successfully disconnected, false otherwise 336 */ 337 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) disconnect(BluetoothDevice device)338 public boolean disconnect(BluetoothDevice device) { 339 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 340 "Need BLUETOOTH_PRIVILEGED permission"); 341 if (DBG) { 342 Log.d(TAG, "disconnect(): " + device); 343 } 344 if (device == null) { 345 return false; 346 } 347 long hiSyncId = mDeviceHiSyncIdMap.getOrDefault(device, 348 BluetoothHearingAid.HI_SYNC_ID_INVALID); 349 350 for (BluetoothDevice storedDevice : mDeviceHiSyncIdMap.keySet()) { 351 if (mDeviceHiSyncIdMap.getOrDefault(storedDevice, 352 BluetoothHearingAid.HI_SYNC_ID_INVALID) == hiSyncId) { 353 synchronized (mStateMachines) { 354 HearingAidStateMachine sm = mStateMachines.get(storedDevice); 355 if (sm == null) { 356 Log.e(TAG, "Ignored disconnect request for " + device 357 + " : no state machine"); 358 continue; 359 } 360 sm.sendMessage(HearingAidStateMachine.DISCONNECT); 361 } 362 if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID 363 && !device.equals(storedDevice)) { 364 break; 365 } 366 } 367 } 368 return true; 369 } 370 getConnectedDevices()371 List<BluetoothDevice> getConnectedDevices() { 372 synchronized (mStateMachines) { 373 List<BluetoothDevice> devices = new ArrayList<>(); 374 for (HearingAidStateMachine sm : mStateMachines.values()) { 375 if (sm.isConnected()) { 376 devices.add(sm.getDevice()); 377 } 378 } 379 return devices; 380 } 381 } 382 383 /** 384 * Check any peer device is connected. 385 * The check considers any peer device is connected. 386 * 387 * @param device the peer device to connect to 388 * @return true if there are any peer device connected. 389 */ 390 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) isConnectedPeerDevices(BluetoothDevice device)391 public boolean isConnectedPeerDevices(BluetoothDevice device) { 392 long hiSyncId = getHiSyncId(device); 393 if (getConnectedPeerDevices(hiSyncId).isEmpty()) { 394 return false; 395 } 396 return true; 397 } 398 399 /** 400 * Check whether can connect to a peer device. 401 * The check considers a number of factors during the evaluation. 402 * 403 * @param device the peer device to connect to 404 * @return true if connection is allowed, otherwise false 405 */ 406 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 407 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) okToConnect(BluetoothDevice device)408 public boolean okToConnect(BluetoothDevice device) { 409 // Check if this is an incoming connection in Quiet mode. 410 if (mAdapterService.isQuietModeEnabled()) { 411 Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled"); 412 return false; 413 } 414 // Check connection policy and accept or reject the connection. 415 int connectionPolicy = getConnectionPolicy(device); 416 int bondState = mAdapterService.getBondState(device); 417 // Allow this connection only if the device is bonded. Any attempt to connect while 418 // bonding would potentially lead to an unauthorized connection. 419 if (bondState != BluetoothDevice.BOND_BONDED) { 420 Log.w(TAG, "okToConnect: return false, bondState=" + bondState); 421 return false; 422 } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN 423 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 424 // Otherwise, reject the connection if connectionPolicy is not valid. 425 Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy); 426 return false; 427 } 428 return true; 429 } 430 getDevicesMatchingConnectionStates(int[] states)431 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 432 ArrayList<BluetoothDevice> devices = new ArrayList<>(); 433 if (states == null) { 434 return devices; 435 } 436 final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 437 if (bondedDevices == null) { 438 return devices; 439 } 440 synchronized (mStateMachines) { 441 for (BluetoothDevice device : bondedDevices) { 442 final ParcelUuid[] featureUuids = device.getUuids(); 443 if (!Utils.arrayContains(featureUuids, BluetoothUuid.HEARING_AID)) { 444 continue; 445 } 446 int connectionState = BluetoothProfile.STATE_DISCONNECTED; 447 HearingAidStateMachine sm = mStateMachines.get(device); 448 if (sm != null) { 449 connectionState = sm.getConnectionState(); 450 } 451 for (int state : states) { 452 if (connectionState == state) { 453 devices.add(device); 454 break; 455 } 456 } 457 } 458 return devices; 459 } 460 } 461 462 /** 463 * Get the list of devices that have state machines. 464 * 465 * @return the list of devices that have state machines 466 */ 467 @VisibleForTesting getDevices()468 List<BluetoothDevice> getDevices() { 469 List<BluetoothDevice> devices = new ArrayList<>(); 470 synchronized (mStateMachines) { 471 for (HearingAidStateMachine sm : mStateMachines.values()) { 472 devices.add(sm.getDevice()); 473 } 474 return devices; 475 } 476 } 477 478 /** 479 * Get the HiSyncIdMap for testing 480 * 481 * @return mDeviceHiSyncIdMap 482 */ 483 @VisibleForTesting getHiSyncIdMap()484 Map<BluetoothDevice, Long> getHiSyncIdMap() { 485 return mDeviceHiSyncIdMap; 486 } 487 488 /** 489 * Get the current connection state of the profile 490 * 491 * @param device is the remote bluetooth device 492 * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected, 493 * {@link BluetoothProfile#STATE_CONNECTING} if this profile is being connected, 494 * {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or 495 * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected 496 */ getConnectionState(BluetoothDevice device)497 public int getConnectionState(BluetoothDevice device) { 498 synchronized (mStateMachines) { 499 HearingAidStateMachine sm = mStateMachines.get(device); 500 if (sm == null) { 501 return BluetoothProfile.STATE_DISCONNECTED; 502 } 503 return sm.getConnectionState(); 504 } 505 } 506 507 /** 508 * Set connection policy of the profile and connects it if connectionPolicy is 509 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is 510 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 511 * 512 * <p> The device should already be paired. 513 * Connection policy can be one of: 514 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 515 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 516 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 517 * 518 * @param device Paired bluetooth device 519 * @param connectionPolicy is the connection policy to set to for this profile 520 * @return true if connectionPolicy is set, false on error 521 */ 522 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) setConnectionPolicy(BluetoothDevice device, int connectionPolicy)523 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { 524 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 525 "Need BLUETOOTH_PRIVILEGED permission"); 526 if (DBG) { 527 Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); 528 } 529 530 if (!mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID, 531 connectionPolicy)) { 532 return false; 533 } 534 if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 535 connect(device); 536 } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 537 disconnect(device); 538 } 539 return true; 540 } 541 542 /** 543 * Get the connection policy of the profile. 544 * 545 * <p> The connection policy can be any of: 546 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 547 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 548 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 549 * 550 * @param device Bluetooth device 551 * @return connection policy of the device 552 * @hide 553 */ 554 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getConnectionPolicy(BluetoothDevice device)555 public int getConnectionPolicy(BluetoothDevice device) { 556 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 557 "Need BLUETOOTH_PRIVILEGED permission"); 558 return mDatabaseManager 559 .getProfileConnectionPolicy(device, BluetoothProfile.HEARING_AID); 560 } 561 562 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) setVolume(int volume)563 void setVolume(int volume) { 564 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 565 "Need BLUETOOTH_PRIVILEGED permission"); 566 mHearingAidNativeInterface.setVolume(volume); 567 } 568 569 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getHiSyncId(BluetoothDevice device)570 public long getHiSyncId(BluetoothDevice device) { 571 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 572 "Need BLUETOOTH_PRIVILEGED permission"); 573 if (device == null) { 574 return BluetoothHearingAid.HI_SYNC_ID_INVALID; 575 } 576 return mDeviceHiSyncIdMap.getOrDefault(device, BluetoothHearingAid.HI_SYNC_ID_INVALID); 577 } 578 getCapabilities(BluetoothDevice device)579 int getCapabilities(BluetoothDevice device) { 580 return mDeviceCapabilitiesMap.getOrDefault(device, -1); 581 } 582 583 @RequiresPermission( 584 allOf = { 585 android.Manifest.permission.BLUETOOTH_SCAN, 586 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 587 }) getAdvertisementServiceData( BluetoothDevice device, AttributionSource attributionSource)588 private AdvertisementServiceData getAdvertisementServiceData( 589 BluetoothDevice device, AttributionSource attributionSource) { 590 AdapterService service = mAdapterService; 591 if (service == null 592 || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getAdvertisementServiceData") 593 || !Utils.checkScanPermissionForDataDelivery(service, attributionSource, TAG)) { 594 return null; 595 } 596 enforceBluetoothPrivilegedPermission(service); 597 598 int capability = service.getAshaCapability(device); 599 int id = service.getAshaTruncatedHiSyncId(device); 600 if (capability < 0) { 601 Log.i(TAG, "device does not have AdvertisementServiceData"); 602 return null; 603 } 604 return new AdvertisementServiceData(capability, id); 605 } 606 607 /** 608 * Remove the active device. 609 * 610 * @param stopAudio whether to stop current media playback. 611 * @return true on success, otherwise false 612 */ removeActiveDevice(boolean stopAudio)613 public boolean removeActiveDevice(boolean stopAudio) { 614 if (DBG) { 615 Log.d(TAG, "removeActiveDevice: stopAudio=" + stopAudio); 616 } 617 synchronized (mStateMachines) { 618 if (mActiveDeviceHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) { 619 reportActiveDevice(null, stopAudio); 620 mActiveDeviceHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID; 621 } 622 } 623 return true; 624 } 625 626 /** 627 * Set the active device. 628 * 629 * @param device the new active device. Should not be null. 630 * @return true on success, otherwise false 631 */ setActiveDevice(BluetoothDevice device)632 public boolean setActiveDevice(BluetoothDevice device) { 633 if (device == null) { 634 Log.e(TAG, "setActiveDevice: device should not be null!"); 635 return removeActiveDevice(true); 636 } 637 if (DBG) { 638 Log.d(TAG, "setActiveDevice: " + device); 639 } 640 synchronized (mStateMachines) { 641 /* No action needed since this is the same device as previousely activated */ 642 if (device.equals(mActiveDevice)) { 643 if (DBG) { 644 Log.d(TAG, "setActiveDevice: The device is already active. Ignoring."); 645 } 646 return true; 647 } 648 649 if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { 650 Log.e(TAG, "setActiveDevice(" + device + "): failed because device not connected"); 651 return false; 652 } 653 Long deviceHiSyncId = mDeviceHiSyncIdMap.getOrDefault(device, 654 BluetoothHearingAid.HI_SYNC_ID_INVALID); 655 if (deviceHiSyncId != mActiveDeviceHiSyncId) { 656 mActiveDeviceHiSyncId = deviceHiSyncId; 657 reportActiveDevice(device, false); 658 } 659 } 660 return true; 661 } 662 663 /** 664 * Get the connected physical Hearing Aid devices that are active 665 * 666 * @return the list of active devices. The first element is the left active 667 * device; the second element is the right active device. If either or both side 668 * is not active, it will be null on that position 669 */ getActiveDevices()670 public List<BluetoothDevice> getActiveDevices() { 671 ArrayList<BluetoothDevice> activeDevices = new ArrayList<>(); 672 activeDevices.add(null); 673 activeDevices.add(null); 674 synchronized (mStateMachines) { 675 if (mActiveDeviceHiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) { 676 return activeDevices; 677 } 678 for (BluetoothDevice device : mDeviceHiSyncIdMap.keySet()) { 679 if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { 680 continue; 681 } 682 if (mDeviceHiSyncIdMap.get(device) == mActiveDeviceHiSyncId) { 683 int deviceSide = getCapabilities(device) & 1; 684 if (deviceSide == BluetoothHearingAid.SIDE_RIGHT) { 685 activeDevices.set(1, device); 686 } else { 687 activeDevices.set(0, device); 688 } 689 } 690 } 691 } 692 return activeDevices; 693 } 694 messageFromNative(HearingAidStackEvent stackEvent)695 void messageFromNative(HearingAidStackEvent stackEvent) { 696 Objects.requireNonNull(stackEvent.device, 697 "Device should never be null, event: " + stackEvent); 698 699 if (stackEvent.type == HearingAidStackEvent.EVENT_TYPE_DEVICE_AVAILABLE) { 700 BluetoothDevice device = stackEvent.device; 701 int capabilities = stackEvent.valueInt1; 702 long hiSyncId = stackEvent.valueLong2; 703 if (DBG) { 704 Log.d(TAG, "Device available: device=" + device + " capabilities=" 705 + capabilities + " hiSyncId=" + hiSyncId); 706 } 707 mDeviceCapabilitiesMap.put(device, capabilities); 708 mDeviceHiSyncIdMap.put(device, hiSyncId); 709 return; 710 } 711 712 synchronized (mStateMachines) { 713 BluetoothDevice device = stackEvent.device; 714 HearingAidStateMachine sm = mStateMachines.get(device); 715 if (sm == null) { 716 if (stackEvent.type == HearingAidStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { 717 switch (stackEvent.valueInt1) { 718 case HearingAidStackEvent.CONNECTION_STATE_CONNECTED: 719 case HearingAidStackEvent.CONNECTION_STATE_CONNECTING: 720 sm = getOrCreateStateMachine(device); 721 break; 722 default: 723 break; 724 } 725 } 726 } 727 if (sm == null) { 728 Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); 729 return; 730 } 731 sm.sendMessage(HearingAidStateMachine.STACK_EVENT, stackEvent); 732 } 733 } 734 notifyActiveDeviceChanged()735 private void notifyActiveDeviceChanged() { 736 Intent intent = new Intent(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED); 737 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mActiveDevice); 738 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 739 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 740 sendBroadcast(intent, BLUETOOTH_CONNECT); 741 } 742 743 /* Notifications of audio device disconnection events. */ 744 private class AudioManagerOnAudioDevicesRemovedCallback extends AudioDeviceCallback { 745 @Override onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)746 public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { 747 for (AudioDeviceInfo deviceInfo : removedDevices) { 748 if (deviceInfo.getType() == AudioDeviceInfo.TYPE_HEARING_AID) { 749 notifyActiveDeviceChanged(); 750 if (DBG) { 751 Log.d(TAG, " onAudioDevicesRemoved: device type: " + deviceInfo.getType()); 752 } 753 if (mAudioManager != null) { 754 mAudioManager.unregisterAudioDeviceCallback(this); 755 } else { 756 Log.w(TAG, "onAudioDevicesRemoved: mAudioManager is null"); 757 } 758 } 759 } 760 } 761 } 762 763 /* Notifications of audio device connection events. */ 764 private class AudioManagerOnAudioDevicesAddedCallback extends AudioDeviceCallback { 765 @Override onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)766 public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { 767 for (AudioDeviceInfo deviceInfo : addedDevices) { 768 if (deviceInfo.getType() == AudioDeviceInfo.TYPE_HEARING_AID) { 769 notifyActiveDeviceChanged(); 770 if (DBG) { 771 Log.d(TAG, " onAudioDevicesAdded: device type: " + deviceInfo.getType()); 772 } 773 if (mAudioManager != null) { 774 mAudioManager.unregisterAudioDeviceCallback(this); 775 } else { 776 Log.w(TAG, "onAudioDevicesAdded: mAudioManager is null"); 777 } 778 } 779 } 780 } 781 } 782 getOrCreateStateMachine(BluetoothDevice device)783 private HearingAidStateMachine getOrCreateStateMachine(BluetoothDevice device) { 784 if (device == null) { 785 Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); 786 return null; 787 } 788 synchronized (mStateMachines) { 789 HearingAidStateMachine sm = mStateMachines.get(device); 790 if (sm != null) { 791 return sm; 792 } 793 // Limit the maximum number of state machines to avoid DoS attack 794 if (mStateMachines.size() >= MAX_HEARING_AID_STATE_MACHINES) { 795 Log.e(TAG, "Maximum number of HearingAid state machines reached: " 796 + MAX_HEARING_AID_STATE_MACHINES); 797 return null; 798 } 799 if (DBG) { 800 Log.d(TAG, "Creating a new state machine for " + device); 801 } 802 sm = HearingAidStateMachine.make(device, this, 803 mHearingAidNativeInterface, mStateMachinesThread.getLooper()); 804 mStateMachines.put(device, sm); 805 return sm; 806 } 807 } 808 809 /** 810 * Report the active device change to the active device manager and the media framework. 811 * 812 * @param device the new active device; or null if no active device 813 * @param stopAudio whether to stop audio when device is null. 814 */ reportActiveDevice(BluetoothDevice device, boolean stopAudio)815 private void reportActiveDevice(BluetoothDevice device, boolean stopAudio) { 816 if (DBG) { 817 Log.d(TAG, "reportActiveDevice: device=" + device + " stopAudio=" + stopAudio); 818 } 819 820 if (device != null && stopAudio) { 821 Log.e(TAG, "Illegal arguments: stopAudio should be false when device is not null!"); 822 stopAudio = false; 823 } 824 825 // Note: This is just a safety check for handling illegal call - setActiveDevice(null). 826 if (device == null && stopAudio 827 && getConnectionState(mActiveDevice) == BluetoothProfile.STATE_CONNECTED) { 828 Log.e(TAG, "Illegal arguments: stopAudio should be false when the active hearing aid " 829 + "is still connected!"); 830 stopAudio = false; 831 } 832 833 BluetoothDevice previousAudioDevice = mActiveDevice; 834 835 mActiveDevice = device; 836 837 BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, 838 BluetoothProfile.HEARING_AID, mAdapterService.obfuscateAddress(device), 839 mAdapterService.getMetricId(device)); 840 841 if (DBG) { 842 Log.d(TAG, "Hearing Aid audio: " + previousAudioDevice + " -> " + device 843 + ". Stop audio: " + stopAudio); 844 } 845 846 if (device != null) { 847 mAudioManager.registerAudioDeviceCallback(mAudioManagerOnAudioDevicesAddedCallback, 848 mHandler); 849 } else { 850 mAudioManager.registerAudioDeviceCallback(mAudioManagerOnAudioDevicesRemovedCallback, 851 mHandler); 852 } 853 854 mAudioManager.handleBluetoothActiveDeviceChanged(device, previousAudioDevice, 855 BluetoothProfileConnectionInfo.createHearingAidInfo(!stopAudio)); 856 } 857 858 // Remove state machine if the bonding for a device is removed 859 private class BondStateChangedReceiver extends BroadcastReceiver { 860 @Override onReceive(Context context, Intent intent)861 public void onReceive(Context context, Intent intent) { 862 if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { 863 return; 864 } 865 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 866 BluetoothDevice.ERROR); 867 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 868 Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); 869 bondStateChanged(device, state); 870 } 871 } 872 873 /** 874 * Process a change in the bonding state for a device. 875 * 876 * @param device the device whose bonding state has changed 877 * @param bondState the new bond state for the device. Possible values are: 878 * {@link BluetoothDevice#BOND_NONE}, 879 * {@link BluetoothDevice#BOND_BONDING}, 880 * {@link BluetoothDevice#BOND_BONDED}. 881 */ 882 @VisibleForTesting bondStateChanged(BluetoothDevice device, int bondState)883 void bondStateChanged(BluetoothDevice device, int bondState) { 884 if (DBG) { 885 Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); 886 } 887 // Remove state machine if the bonding for a device is removed 888 if (bondState != BluetoothDevice.BOND_NONE) { 889 return; 890 } 891 mDeviceHiSyncIdMap.remove(device); 892 synchronized (mStateMachines) { 893 HearingAidStateMachine sm = mStateMachines.get(device); 894 if (sm == null) { 895 return; 896 } 897 if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { 898 Log.i(TAG, "Disconnecting device because it was unbonded."); 899 disconnect(device); 900 return; 901 } 902 removeStateMachine(device); 903 } 904 } 905 removeStateMachine(BluetoothDevice device)906 private void removeStateMachine(BluetoothDevice device) { 907 synchronized (mStateMachines) { 908 HearingAidStateMachine sm = mStateMachines.get(device); 909 if (sm == null) { 910 Log.w(TAG, "removeStateMachine: device " + device 911 + " does not have a state machine"); 912 return; 913 } 914 Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); 915 sm.doQuit(); 916 sm.cleanup(); 917 mStateMachines.remove(device); 918 } 919 } 920 921 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getConnectedPeerDevices(long hiSyncId)922 public List<BluetoothDevice> getConnectedPeerDevices(long hiSyncId) { 923 List<BluetoothDevice> result = new ArrayList<>(); 924 for (BluetoothDevice peerDevice : getConnectedDevices()) { 925 if (getHiSyncId(peerDevice) == hiSyncId) { 926 result.add(peerDevice); 927 } 928 } 929 return result; 930 } 931 connectionStateChanged(BluetoothDevice device, int fromState, int toState)932 synchronized void connectionStateChanged(BluetoothDevice device, int fromState, 933 int toState) { 934 if ((device == null) || (fromState == toState)) { 935 Log.e(TAG, "connectionStateChanged: unexpected invocation. device=" + device 936 + " fromState=" + fromState + " toState=" + toState); 937 return; 938 } 939 if (toState == BluetoothProfile.STATE_CONNECTED) { 940 long myHiSyncId = getHiSyncId(device); 941 if (myHiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID 942 || getConnectedPeerDevices(myHiSyncId).size() == 1) { 943 // Log hearing aid connection event if we are the first device in a set 944 // Or when the hiSyncId has not been found 945 MetricsLogger.logProfileConnectionEvent( 946 BluetoothMetricsProto.ProfileId.HEARING_AID); 947 } 948 if (!mHiSyncIdConnectedMap.getOrDefault(myHiSyncId, false)) { 949 mHiSyncIdConnectedMap.put(myHiSyncId, true); 950 } 951 } 952 if (fromState == BluetoothProfile.STATE_CONNECTED && getConnectedDevices().isEmpty()) { 953 long myHiSyncId = getHiSyncId(device); 954 mHiSyncIdConnectedMap.put(myHiSyncId, false); 955 // ActiveDeviceManager will call removeActiveDevice(). 956 } 957 // Check if the device is disconnected - if unbond, remove the state machine 958 if (toState == BluetoothProfile.STATE_DISCONNECTED) { 959 int bondState = mAdapterService.getBondState(device); 960 if (bondState == BluetoothDevice.BOND_NONE) { 961 if (DBG) { 962 Log.d(TAG, device + " is unbond. Remove state machine"); 963 } 964 removeStateMachine(device); 965 } 966 } 967 } 968 969 /** 970 * Binder object: must be a static class or memory leak may occur 971 */ 972 @VisibleForTesting 973 static class BluetoothHearingAidBinder extends IBluetoothHearingAid.Stub 974 implements IProfileServiceBinder { 975 @VisibleForTesting 976 boolean mIsTesting = false; 977 private HearingAidService mService; 978 979 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getService(AttributionSource source)980 private HearingAidService getService(AttributionSource source) { 981 if (mIsTesting) { 982 return mService; 983 } 984 if (!Utils.checkServiceAvailable(mService, TAG) 985 || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG) 986 || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) { 987 return null; 988 } 989 return mService; 990 } 991 BluetoothHearingAidBinder(HearingAidService svc)992 BluetoothHearingAidBinder(HearingAidService svc) { 993 mService = svc; 994 } 995 996 @Override cleanup()997 public void cleanup() { 998 mService = null; 999 } 1000 1001 @Override connect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1002 public void connect(BluetoothDevice device, AttributionSource source, 1003 SynchronousResultReceiver receiver) { 1004 try { 1005 HearingAidService service = getService(source); 1006 boolean result = false; 1007 if (service != null) { 1008 result = service.connect(device); 1009 } 1010 receiver.send(result); 1011 } catch (RuntimeException e) { 1012 receiver.propagateException(e); 1013 } 1014 } 1015 1016 @Override disconnect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1017 public void disconnect(BluetoothDevice device, AttributionSource source, 1018 SynchronousResultReceiver receiver) { 1019 try { 1020 HearingAidService service = getService(source); 1021 boolean result = false; 1022 if (service != null) { 1023 result = service.disconnect(device); 1024 } 1025 receiver.send(result); 1026 } catch (RuntimeException e) { 1027 receiver.propagateException(e); 1028 } 1029 } 1030 1031 @Override getConnectedDevices(AttributionSource source, SynchronousResultReceiver receiver)1032 public void getConnectedDevices(AttributionSource source, 1033 SynchronousResultReceiver receiver) { 1034 try { 1035 HearingAidService service = getService(source); 1036 List<BluetoothDevice> devices = new ArrayList<>(); 1037 if (service != null) { 1038 devices = service.getConnectedDevices(); 1039 } 1040 receiver.send(devices); 1041 } catch (RuntimeException e) { 1042 receiver.propagateException(e); 1043 } 1044 } 1045 1046 @Override getDevicesMatchingConnectionStates(int[] states, AttributionSource source, SynchronousResultReceiver receiver)1047 public void getDevicesMatchingConnectionStates(int[] states, 1048 AttributionSource source, SynchronousResultReceiver receiver) { 1049 try { 1050 HearingAidService service = getService(source); 1051 List<BluetoothDevice> devices = new ArrayList<>(); 1052 if (service != null) { 1053 devices = service.getDevicesMatchingConnectionStates(states); 1054 } 1055 receiver.send(devices); 1056 } catch (RuntimeException e) { 1057 receiver.propagateException(e); 1058 } 1059 } 1060 1061 @Override getConnectionState(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1062 public void getConnectionState(BluetoothDevice device, AttributionSource source, 1063 SynchronousResultReceiver receiver) { 1064 try { 1065 HearingAidService service = getService(source); 1066 int state = BluetoothProfile.STATE_DISCONNECTED; 1067 if (service != null) { 1068 state = service.getConnectionState(device); 1069 } 1070 receiver.send(state); 1071 } catch (RuntimeException e) { 1072 receiver.propagateException(e); 1073 } 1074 } 1075 1076 @Override setActiveDevice(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1077 public void setActiveDevice(BluetoothDevice device, AttributionSource source, 1078 SynchronousResultReceiver receiver) { 1079 try { 1080 HearingAidService service = getService(source); 1081 boolean result = false; 1082 if (service != null) { 1083 if (device == null) { 1084 result = service.removeActiveDevice(false); 1085 } else { 1086 result = service.setActiveDevice(device); 1087 } 1088 } 1089 receiver.send(result); 1090 } catch (RuntimeException e) { 1091 receiver.propagateException(e); 1092 } 1093 } 1094 1095 @Override getActiveDevices(AttributionSource source, SynchronousResultReceiver receiver)1096 public void getActiveDevices(AttributionSource source, 1097 SynchronousResultReceiver receiver) { 1098 try { 1099 HearingAidService service = getService(source); 1100 List<BluetoothDevice> devices = new ArrayList<>(); 1101 if (service != null) { 1102 devices = service.getActiveDevices(); 1103 } 1104 receiver.send(devices); 1105 } catch (RuntimeException e) { 1106 receiver.propagateException(e); 1107 } 1108 } 1109 1110 @Override setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source, SynchronousResultReceiver receiver)1111 public void setConnectionPolicy(BluetoothDevice device, int connectionPolicy, 1112 AttributionSource source, SynchronousResultReceiver receiver) { 1113 try { 1114 HearingAidService service = getService(source); 1115 boolean result = false; 1116 if (service != null) { 1117 result = service.setConnectionPolicy(device, connectionPolicy); 1118 } 1119 receiver.send(result); 1120 } catch (RuntimeException e) { 1121 receiver.propagateException(e); 1122 } 1123 } 1124 1125 @Override getConnectionPolicy(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1126 public void getConnectionPolicy(BluetoothDevice device, AttributionSource source, 1127 SynchronousResultReceiver receiver) { 1128 try { 1129 HearingAidService service = getService(source); 1130 int policy = BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 1131 if (service != null) { 1132 policy = service.getConnectionPolicy(device); 1133 } 1134 receiver.send(policy); 1135 } catch (RuntimeException e) { 1136 receiver.propagateException(e); 1137 } 1138 } 1139 1140 @Override setVolume(int volume, AttributionSource source, SynchronousResultReceiver receiver)1141 public void setVolume(int volume, AttributionSource source, 1142 SynchronousResultReceiver receiver) { 1143 try { 1144 HearingAidService service = getService(source); 1145 if (service != null) { 1146 service.setVolume(volume); 1147 } 1148 receiver.send(null); 1149 } catch (RuntimeException e) { 1150 receiver.propagateException(e); 1151 } 1152 } 1153 1154 @Override getHiSyncId(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1155 public void getHiSyncId(BluetoothDevice device, AttributionSource source, 1156 SynchronousResultReceiver receiver) { 1157 try { 1158 HearingAidService service = getService(source); 1159 long id = BluetoothHearingAid.HI_SYNC_ID_INVALID; 1160 if (service != null) { 1161 id = service.getHiSyncId(device); 1162 } 1163 receiver.send(id); 1164 } catch (RuntimeException e) { 1165 receiver.propagateException(e); 1166 } 1167 } 1168 1169 @Override getDeviceSide(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1170 public void getDeviceSide(BluetoothDevice device, AttributionSource source, 1171 SynchronousResultReceiver receiver) { 1172 try { 1173 HearingAidService service = getService(source); 1174 int side = BluetoothHearingAid.SIDE_UNKNOWN; 1175 if (service != null) { 1176 side = service.getCapabilities(device); 1177 if (side != BluetoothHearingAid.SIDE_UNKNOWN) { 1178 side &= 1; 1179 } 1180 } 1181 receiver.send(side); 1182 } catch (RuntimeException e) { 1183 receiver.propagateException(e); 1184 } 1185 } 1186 1187 @Override getDeviceMode(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1188 public void getDeviceMode(BluetoothDevice device, AttributionSource source, 1189 SynchronousResultReceiver receiver) { 1190 try { 1191 HearingAidService service = getService(source); 1192 int mode = BluetoothHearingAid.MODE_UNKNOWN; 1193 if (service != null) { 1194 mode = service.getCapabilities(device); 1195 if (mode != BluetoothHearingAid.MODE_UNKNOWN) { 1196 mode = mode >> 1 & 1; 1197 } 1198 } 1199 receiver.send(mode); 1200 } catch (RuntimeException e) { 1201 receiver.propagateException(e); 1202 } 1203 } 1204 1205 @RequiresPermission( 1206 allOf = { 1207 android.Manifest.permission.BLUETOOTH_SCAN, 1208 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 1209 }) 1210 @Override getAdvertisementServiceData( BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1211 public void getAdvertisementServiceData( 1212 BluetoothDevice device, 1213 AttributionSource source, 1214 SynchronousResultReceiver receiver) { 1215 try { 1216 if (!Utils.checkServiceAvailable(mService, TAG) 1217 || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG) 1218 || !Utils.checkScanPermissionForDataDelivery(mService, source, TAG)) { 1219 receiver.send(null); 1220 return; 1221 } 1222 receiver.send(mService.getAdvertisementServiceData(device, source)); 1223 } catch (RuntimeException e) { 1224 receiver.propagateException(e); 1225 } 1226 } 1227 } 1228 1229 @Override dump(StringBuilder sb)1230 public void dump(StringBuilder sb) { 1231 super.dump(sb); 1232 for (HearingAidStateMachine sm : mStateMachines.values()) { 1233 sm.dump(sb); 1234 } 1235 } 1236 } 1237