1 /* 2 * Copyright 2021 HIMSA II K/S - www.himsa.com. 3 * Represented by EHIMA - www.ehima.com 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.bluetooth.hap; 19 20 import static android.Manifest.permission.BLUETOOTH_CONNECT; 21 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; 22 23 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; 24 25 import android.annotation.Nullable; 26 import android.bluetooth.BluetoothCsipSetCoordinator; 27 import android.bluetooth.BluetoothDevice; 28 import android.bluetooth.BluetoothHapClient; 29 import android.bluetooth.BluetoothHapPresetInfo; 30 import android.bluetooth.BluetoothLeAudio; 31 import android.bluetooth.BluetoothProfile; 32 import android.bluetooth.BluetoothStatusCodes; 33 import android.bluetooth.BluetoothUuid; 34 import android.bluetooth.IBluetoothHapClient; 35 import android.bluetooth.IBluetoothHapClientCallback; 36 import android.content.AttributionSource; 37 import android.content.BroadcastReceiver; 38 import android.content.Context; 39 import android.content.Intent; 40 import android.content.IntentFilter; 41 import android.os.HandlerThread; 42 import android.os.ParcelUuid; 43 import android.os.RemoteCallbackList; 44 import android.os.RemoteException; 45 import android.sysprop.BluetoothProperties; 46 import android.util.Log; 47 48 import com.android.bluetooth.Utils; 49 import com.android.bluetooth.btservice.AdapterService; 50 import com.android.bluetooth.btservice.ProfileService; 51 import com.android.bluetooth.btservice.ServiceFactory; 52 import com.android.bluetooth.btservice.storage.DatabaseManager; 53 import com.android.bluetooth.csip.CsipSetCoordinatorService; 54 import com.android.internal.annotations.VisibleForTesting; 55 import com.android.modules.utils.SynchronousResultReceiver; 56 57 import java.math.BigInteger; 58 import java.util.ArrayList; 59 import java.util.Collections; 60 import java.util.HashMap; 61 import java.util.List; 62 import java.util.ListIterator; 63 import java.util.Map; 64 import java.util.Objects; 65 66 /** 67 * Provides Bluetooth Hearing Access profile, as a service. 68 * @hide 69 */ 70 public class HapClientService extends ProfileService { 71 private static final boolean DBG = true; 72 private static final String TAG = "HapClientService"; 73 74 // Upper limit of all HearingAccess devices: Bonded or Connected 75 private static final int MAX_HEARING_ACCESS_STATE_MACHINES = 10; 76 private static final int SM_THREAD_JOIN_TIMEOUT_MS = 1000; 77 private static HapClientService sHapClient; 78 private final Map<BluetoothDevice, HapClientStateMachine> mStateMachines = 79 new HashMap<>(); 80 @VisibleForTesting 81 HapClientNativeInterface mHapClientNativeInterface; 82 private AdapterService mAdapterService; 83 private DatabaseManager mDatabaseManager; 84 private HandlerThread mStateMachinesThread; 85 private BroadcastReceiver mBondStateChangedReceiver; 86 private BroadcastReceiver mConnectionStateChangedReceiver; 87 88 private final Map<BluetoothDevice, Integer> mDeviceCurrentPresetMap = new HashMap<>(); 89 private final Map<BluetoothDevice, Integer> mDeviceFeaturesMap = new HashMap<>(); 90 private final Map<BluetoothDevice, List<BluetoothHapPresetInfo>> mPresetsMap = 91 new HashMap<>(); 92 93 @VisibleForTesting 94 RemoteCallbackList<IBluetoothHapClientCallback> mCallbacks; 95 96 @VisibleForTesting 97 ServiceFactory mFactory = new ServiceFactory(); 98 isEnabled()99 public static boolean isEnabled() { 100 return BluetoothProperties.isProfileHapClientEnabled().orElse(false); 101 } 102 103 @VisibleForTesting setHapClient(HapClientService instance)104 static synchronized void setHapClient(HapClientService instance) { 105 if (DBG) { 106 Log.d(TAG, "setHapClient(): set to: " + instance); 107 } 108 sHapClient = instance; 109 } 110 111 /** 112 * Get the HapClientService instance 113 * @return HapClientService instance 114 */ getHapClientService()115 public static synchronized HapClientService getHapClientService() { 116 if (sHapClient == null) { 117 Log.w(TAG, "getHapClientService(): service is NULL"); 118 return null; 119 } 120 121 if (!sHapClient.isAvailable()) { 122 Log.w(TAG, "getHapClientService(): service is not available"); 123 return null; 124 } 125 return sHapClient; 126 } 127 128 @Override create()129 protected void create() { 130 if (DBG) { 131 Log.d(TAG, "create()"); 132 } 133 } 134 135 @Override cleanup()136 protected void cleanup() { 137 if (DBG) { 138 Log.d(TAG, "cleanup()"); 139 } 140 } 141 142 @Override initBinder()143 protected IProfileServiceBinder initBinder() { 144 return new BluetoothHapClientBinder(this); 145 } 146 147 @Override start()148 protected boolean start() { 149 if (DBG) { 150 Log.d(TAG, "start()"); 151 } 152 153 if (sHapClient != null) { 154 throw new IllegalStateException("start() called twice"); 155 } 156 157 // Get AdapterService, HapClientNativeInterface, DatabaseManager, AudioManager. 158 // None of them can be null. 159 mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), 160 "AdapterService cannot be null when HapClientService starts"); 161 mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(), 162 "DatabaseManager cannot be null when HapClientService starts"); 163 mHapClientNativeInterface = Objects.requireNonNull( 164 HapClientNativeInterface.getInstance(), 165 "HapClientNativeInterface cannot be null when HapClientService starts"); 166 167 // Start handler thread for state machines 168 mStateMachines.clear(); 169 mStateMachinesThread = new HandlerThread("HapClientService.StateMachines"); 170 mStateMachinesThread.start(); 171 172 // Setup broadcast receivers 173 IntentFilter filter = new IntentFilter(); 174 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 175 mBondStateChangedReceiver = new BondStateChangedReceiver(); 176 registerReceiver(mBondStateChangedReceiver, filter); 177 filter = new IntentFilter(); 178 filter.addAction(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED); 179 mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver(); 180 registerReceiver(mConnectionStateChangedReceiver, filter, Context.RECEIVER_NOT_EXPORTED); 181 182 mCallbacks = new RemoteCallbackList<IBluetoothHapClientCallback>(); 183 184 // Initialize native interface 185 mHapClientNativeInterface.init(); 186 187 // Mark service as started 188 setHapClient(this); 189 190 return true; 191 } 192 193 @Override stop()194 protected boolean stop() { 195 if (DBG) { 196 Log.d(TAG, "stop()"); 197 } 198 if (sHapClient == null) { 199 Log.w(TAG, "stop() called before start()"); 200 return true; 201 } 202 203 // Marks service as stopped 204 setHapClient(null); 205 206 // Unregister broadcast receivers 207 unregisterReceiver(mBondStateChangedReceiver); 208 mBondStateChangedReceiver = null; 209 unregisterReceiver(mConnectionStateChangedReceiver); 210 mConnectionStateChangedReceiver = null; 211 212 // Destroy state machines and stop handler thread 213 synchronized (mStateMachines) { 214 for (HapClientStateMachine sm : mStateMachines.values()) { 215 sm.doQuit(); 216 sm.cleanup(); 217 } 218 mStateMachines.clear(); 219 } 220 221 if (mStateMachinesThread != null) { 222 try { 223 mStateMachinesThread.quitSafely(); 224 mStateMachinesThread.join(SM_THREAD_JOIN_TIMEOUT_MS); 225 mStateMachinesThread = null; 226 } catch (InterruptedException e) { 227 // Do not rethrow as we are shutting down anyway 228 } 229 } 230 231 // Cleanup GATT interface 232 mHapClientNativeInterface.cleanup(); 233 mHapClientNativeInterface = null; 234 235 // Cleanup the internals 236 mDeviceCurrentPresetMap.clear(); 237 mDeviceFeaturesMap.clear(); 238 mPresetsMap.clear(); 239 240 if (mCallbacks != null) { 241 mCallbacks.kill(); 242 } 243 244 // Clear AdapterService 245 mAdapterService = null; 246 247 return true; 248 } 249 250 @VisibleForTesting bondStateChanged(BluetoothDevice device, int bondState)251 void bondStateChanged(BluetoothDevice device, int bondState) { 252 if (DBG) { 253 Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); 254 } 255 256 // Remove state machine if the bonding for a device is removed 257 if (bondState != BluetoothDevice.BOND_NONE) { 258 return; 259 } 260 261 mDeviceCurrentPresetMap.remove(device); 262 mDeviceFeaturesMap.remove(device); 263 mPresetsMap.remove(device); 264 265 synchronized (mStateMachines) { 266 HapClientStateMachine sm = mStateMachines.get(device); 267 if (sm == null) { 268 return; 269 } 270 if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { 271 Log.i(TAG, "Disconnecting device because it was unbonded."); 272 disconnect(device); 273 return; 274 } 275 removeStateMachine(device); 276 } 277 } 278 removeStateMachine(BluetoothDevice device)279 private void removeStateMachine(BluetoothDevice device) { 280 synchronized (mStateMachines) { 281 HapClientStateMachine sm = mStateMachines.get(device); 282 if (sm == null) { 283 Log.w(TAG, "removeStateMachine: device " + device 284 + " does not have a state machine"); 285 return; 286 } 287 Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); 288 sm.doQuit(); 289 sm.cleanup(); 290 mStateMachines.remove(device); 291 } 292 } 293 getDevicesMatchingConnectionStates(int[] states)294 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 295 enforceCallingOrSelfPermission(BLUETOOTH_CONNECT, "Need BLUETOOTH_CONNECT permission"); 296 ArrayList<BluetoothDevice> devices = new ArrayList<>(); 297 if (states == null) { 298 return devices; 299 } 300 final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 301 if (bondedDevices == null) { 302 return devices; 303 } 304 synchronized (mStateMachines) { 305 for (BluetoothDevice device : bondedDevices) { 306 final ParcelUuid[] featureUuids = device.getUuids(); 307 if (!Utils.arrayContains(featureUuids, BluetoothUuid.HAS)) { 308 continue; 309 } 310 int connectionState = BluetoothProfile.STATE_DISCONNECTED; 311 HapClientStateMachine sm = mStateMachines.get(device); 312 if (sm != null) { 313 connectionState = sm.getConnectionState(); 314 } 315 for (int state : states) { 316 if (connectionState == state) { 317 devices.add(device); 318 break; 319 } 320 } 321 } 322 return devices; 323 } 324 } 325 getConnectedDevices()326 List<BluetoothDevice> getConnectedDevices() { 327 synchronized (mStateMachines) { 328 List<BluetoothDevice> devices = new ArrayList<>(); 329 for (HapClientStateMachine sm : mStateMachines.values()) { 330 if (sm.isConnected()) { 331 devices.add(sm.getDevice()); 332 } 333 } 334 return devices; 335 } 336 } 337 338 /** 339 * Get the current connection state of the profile 340 * 341 * @param device is the remote bluetooth device 342 * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected, 343 * {@link BluetoothProfile#STATE_CONNECTING} if this profile is being connected, 344 * {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or 345 * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected 346 */ getConnectionState(BluetoothDevice device)347 public int getConnectionState(BluetoothDevice device) { 348 enforceCallingOrSelfPermission(BLUETOOTH_CONNECT, "Need BLUETOOTH_CONNECT permission"); 349 synchronized (mStateMachines) { 350 HapClientStateMachine sm = mStateMachines.get(device); 351 if (sm == null) { 352 return BluetoothProfile.STATE_DISCONNECTED; 353 } 354 return sm.getConnectionState(); 355 } 356 } 357 358 /** 359 * Set connection policy of the profile and connects it if connectionPolicy is 360 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is 361 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 362 * 363 * <p> The device should already be paired. 364 * Connection policy can be one of: 365 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 366 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 367 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 368 * 369 * @param device the remote device 370 * @param connectionPolicy is the connection policy to set to for this profile 371 * @return true on success, otherwise false 372 */ setConnectionPolicy(BluetoothDevice device, int connectionPolicy)373 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { 374 enforceBluetoothPrivilegedPermission(this); 375 if (DBG) { 376 Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); 377 } 378 mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.HAP_CLIENT, 379 connectionPolicy); 380 if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 381 connect(device); 382 } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 383 disconnect(device); 384 } 385 return true; 386 } 387 388 /** 389 * Get the connection policy of the profile. 390 * 391 * <p> The connection policy can be any of: 392 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 393 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 394 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 395 * 396 * @param device Bluetooth device 397 * @return connection policy of the device 398 * @hide 399 */ getConnectionPolicy(BluetoothDevice device)400 public int getConnectionPolicy(BluetoothDevice device) { 401 return mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HAP_CLIENT); 402 } 403 404 /** 405 * Check whether can connect to a peer device. 406 * The check considers a number of factors during the evaluation. 407 * 408 * @param device the peer device to connect to 409 * @return true if connection is allowed, otherwise false 410 */ 411 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) okToConnect(BluetoothDevice device)412 public boolean okToConnect(BluetoothDevice device) { 413 // Check if this is an incoming connection in Quiet mode. 414 if (mAdapterService.isQuietModeEnabled()) { 415 Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled"); 416 return false; 417 } 418 // Check connection policy and accept or reject the connection. 419 int connectionPolicy = getConnectionPolicy(device); 420 int bondState = mAdapterService.getBondState(device); 421 // Allow this connection only if the device is bonded. Any attempt to connect while 422 // bonding would potentially lead to an unauthorized connection. 423 if (bondState != BluetoothDevice.BOND_BONDED) { 424 Log.w(TAG, "okToConnect: return false, bondState=" + bondState); 425 return false; 426 } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN 427 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 428 // Otherwise, reject the connection if connectionPolicy is not valid. 429 Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy); 430 return false; 431 } 432 return true; 433 } 434 435 @VisibleForTesting connectionStateChanged(BluetoothDevice device, int fromState, int toState)436 synchronized void connectionStateChanged(BluetoothDevice device, int fromState, 437 int toState) { 438 if ((device == null) || (fromState == toState)) { 439 Log.e(TAG, "connectionStateChanged: unexpected invocation. device=" + device 440 + " fromState=" + fromState + " toState=" + toState); 441 return; 442 } 443 444 // Check if the device is disconnected - if unbond, remove the state machine 445 if (toState == BluetoothProfile.STATE_DISCONNECTED) { 446 int bondState = mAdapterService.getBondState(device); 447 if (bondState == BluetoothDevice.BOND_NONE) { 448 if (DBG) { 449 Log.d(TAG, device + " is unbond. Remove state machine"); 450 } 451 removeStateMachine(device); 452 } 453 } 454 } 455 456 /** 457 * Connects the hearing access service client to the passed in device 458 * 459 * @param device is the device with which we will connect the hearing access service client 460 * @return true if hearing access service client successfully connected, false otherwise 461 */ connect(BluetoothDevice device)462 public boolean connect(BluetoothDevice device) { 463 enforceBluetoothPrivilegedPermission(this); 464 if (DBG) { 465 Log.d(TAG, "connect(): " + device); 466 } 467 if (device == null) { 468 return false; 469 } 470 471 if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 472 return false; 473 } 474 ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device); 475 if (!Utils.arrayContains(featureUuids, BluetoothUuid.HAS)) { 476 Log.e(TAG, "Cannot connect to " + device 477 + " : Remote does not have Hearing Access Service UUID"); 478 return false; 479 } 480 synchronized (mStateMachines) { 481 HapClientStateMachine smConnect = getOrCreateStateMachine(device); 482 if (smConnect == null) { 483 Log.e(TAG, "Cannot connect to " + device + " : no state machine"); 484 } 485 smConnect.sendMessage(HapClientStateMachine.CONNECT); 486 } 487 488 return true; 489 } 490 491 /** 492 * Disconnects hearing access service client for the passed in device 493 * 494 * @param device is the device with which we want to disconnect the hearing access service 495 * client 496 * @return true if hearing access service client successfully disconnected, false otherwise 497 */ disconnect(BluetoothDevice device)498 public boolean disconnect(BluetoothDevice device) { 499 enforceBluetoothPrivilegedPermission(this); 500 if (DBG) { 501 Log.d(TAG, "disconnect(): " + device); 502 } 503 if (device == null) { 504 return false; 505 } 506 synchronized (mStateMachines) { 507 HapClientStateMachine sm = mStateMachines.get(device); 508 if (sm != null) { 509 sm.sendMessage(HapClientStateMachine.DISCONNECT); 510 } 511 } 512 513 return true; 514 } 515 getOrCreateStateMachine(BluetoothDevice device)516 private HapClientStateMachine getOrCreateStateMachine(BluetoothDevice device) { 517 if (device == null) { 518 Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); 519 return null; 520 } 521 synchronized (mStateMachines) { 522 HapClientStateMachine sm = mStateMachines.get(device); 523 if (sm != null) { 524 return sm; 525 } 526 // Limit the maximum number of state machines to avoid DoS attack 527 if (mStateMachines.size() >= MAX_HEARING_ACCESS_STATE_MACHINES) { 528 Log.e(TAG, "Maximum number of HearingAccess state machines reached: " 529 + MAX_HEARING_ACCESS_STATE_MACHINES); 530 return null; 531 } 532 if (DBG) { 533 Log.d(TAG, "Creating a new state machine for " + device); 534 } 535 sm = HapClientStateMachine.make(device, this, 536 mHapClientNativeInterface, mStateMachinesThread.getLooper()); 537 mStateMachines.put(device, sm); 538 return sm; 539 } 540 } 541 542 /** 543 * Gets the hearing access device group of the passed device 544 * 545 * @param device is the device with which we want to get the group identifier for 546 * @return group ID if device is part of the coordinated group, 0 otherwise 547 */ getHapGroup(BluetoothDevice device)548 public int getHapGroup(BluetoothDevice device) { 549 CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService(); 550 551 if (csipClient != null) { 552 Map<Integer, ParcelUuid> groups = csipClient.getGroupUuidMapByDevice(device); 553 for (Map.Entry<Integer, ParcelUuid> entry : groups.entrySet()) { 554 if (entry.getValue().equals(BluetoothUuid.CAP)) { 555 return entry.getKey(); 556 } 557 } 558 } 559 return BluetoothCsipSetCoordinator.GROUP_ID_INVALID; 560 } 561 562 /** 563 * Gets the currently active preset index for a HA device 564 * 565 * @param device is the device for which we want to get the currently active preset 566 * @return active preset index 567 */ getActivePresetIndex(BluetoothDevice device)568 public int getActivePresetIndex(BluetoothDevice device) { 569 return mDeviceCurrentPresetMap.getOrDefault(device, 570 BluetoothHapClient.PRESET_INDEX_UNAVAILABLE); 571 } 572 573 /** 574 * Gets the currently active preset info for a HA device 575 * 576 * @param device is the device for which we want to get the currently active preset info 577 * @return active preset info or null if not available 578 */ getActivePresetInfo(BluetoothDevice device)579 public @Nullable BluetoothHapPresetInfo getActivePresetInfo(BluetoothDevice device) { 580 int index = getActivePresetIndex(device); 581 if (index == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) return null; 582 583 List<BluetoothHapPresetInfo> current_presets = mPresetsMap.get(device); 584 if (current_presets != null) { 585 for (BluetoothHapPresetInfo preset : current_presets) { 586 if (preset.getIndex() == index) { 587 return preset; 588 } 589 } 590 } 591 592 return null; 593 } 594 595 /** 596 * Selects the currently active preset for a HA device 597 * 598 * @param device is the device for which we want to set the active preset 599 * @param presetIndex is an index of one of the available presets 600 */ selectPreset(BluetoothDevice device, int presetIndex)601 public void selectPreset(BluetoothDevice device, int presetIndex) { 602 if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) { 603 if (mCallbacks != null) { 604 int n = mCallbacks.beginBroadcast(); 605 for (int i = 0; i < n; i++) { 606 try { 607 mCallbacks.getBroadcastItem(i).onPresetSelectionFailed(device, 608 BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX); 609 } catch (RemoteException e) { 610 continue; 611 } 612 } 613 mCallbacks.finishBroadcast(); 614 } 615 return; 616 } 617 618 mHapClientNativeInterface.selectActivePreset(device, presetIndex); 619 } 620 621 /** 622 * Selects the currently active preset for a HA device group. 623 * 624 * @param groupId is the device group identifier for which want to set the active preset 625 * @param presetIndex is an index of one of the available presets 626 */ selectPresetForGroup(int groupId, int presetIndex)627 public void selectPresetForGroup(int groupId, int presetIndex) { 628 int status = BluetoothStatusCodes.SUCCESS; 629 630 if (!isGroupIdValid(groupId)) { 631 status = BluetoothStatusCodes.ERROR_CSIP_INVALID_GROUP_ID; 632 } else if (!isPresetIndexValid(groupId, presetIndex)) { 633 status = BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX; 634 } 635 636 if (status != BluetoothStatusCodes.SUCCESS) { 637 if (mCallbacks != null) { 638 int n = mCallbacks.beginBroadcast(); 639 for (int i = 0; i < n; i++) { 640 try { 641 mCallbacks.getBroadcastItem(i) 642 .onPresetSelectionForGroupFailed(groupId, status); 643 } catch (RemoteException e) { 644 continue; 645 } 646 } 647 mCallbacks.finishBroadcast(); 648 } 649 return; 650 } 651 652 mHapClientNativeInterface.groupSelectActivePreset(groupId, presetIndex); 653 } 654 655 /** 656 * Sets the next preset as a currently active preset for a HA device 657 * 658 * @param device is the device for which we want to set the active preset 659 */ switchToNextPreset(BluetoothDevice device)660 public void switchToNextPreset(BluetoothDevice device) { 661 mHapClientNativeInterface.nextActivePreset(device); 662 } 663 664 /** 665 * Sets the next preset as a currently active preset for a HA device group 666 * 667 * @param groupId is the device group identifier for which want to set the active preset 668 */ switchToNextPresetForGroup(int groupId)669 public void switchToNextPresetForGroup(int groupId) { 670 mHapClientNativeInterface.groupNextActivePreset(groupId); 671 } 672 673 /** 674 * Sets the previous preset as a currently active preset for a HA device 675 * 676 * @param device is the device for which we want to set the active preset 677 */ switchToPreviousPreset(BluetoothDevice device)678 public void switchToPreviousPreset(BluetoothDevice device) { 679 mHapClientNativeInterface.previousActivePreset(device); 680 } 681 682 /** 683 * Sets the previous preset as a currently active preset for a HA device group 684 * 685 * @param groupId is the device group identifier for which want to set the active preset 686 */ switchToPreviousPresetForGroup(int groupId)687 public void switchToPreviousPresetForGroup(int groupId) { 688 mHapClientNativeInterface.groupPreviousActivePreset(groupId); 689 } 690 691 /** 692 * Requests the preset name 693 * 694 * @param device is the device for which we want to get the preset name 695 * @param presetIndex is an index of one of the available presets 696 * @return a preset Info corresponding to the requested preset index or null if not available 697 */ getPresetInfo(BluetoothDevice device, int presetIndex)698 public @Nullable BluetoothHapPresetInfo getPresetInfo(BluetoothDevice device, int presetIndex) { 699 BluetoothHapPresetInfo defaultValue = null; 700 if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) return defaultValue; 701 702 if (Utils.isPtsTestMode()) { 703 /* We want native to be called for PTS testing even we have all 704 * the data in the cache here 705 */ 706 mHapClientNativeInterface.getPresetInfo(device, presetIndex); 707 } 708 List<BluetoothHapPresetInfo> current_presets = mPresetsMap.get(device); 709 if (current_presets != null) { 710 for (BluetoothHapPresetInfo preset : current_presets) { 711 if (preset.getIndex() == presetIndex) { 712 return preset; 713 } 714 } 715 } 716 717 return defaultValue; 718 } 719 720 /** 721 * Requests all presets info 722 * 723 * @param device is the device for which we want to get all presets info 724 * @return a list of all presets Info 725 */ getAllPresetInfo(BluetoothDevice device)726 public List<BluetoothHapPresetInfo> getAllPresetInfo(BluetoothDevice device) { 727 if (mPresetsMap.containsKey(device)) { 728 return mPresetsMap.get(device); 729 } 730 return Collections.emptyList(); 731 } 732 733 /** 734 * Requests features 735 * 736 * @param device is the device for which we want to get features 737 * @return integer with feature bits set 738 */ getFeatures(BluetoothDevice device)739 public int getFeatures(BluetoothDevice device) { 740 if (mDeviceFeaturesMap.containsKey(device)) { 741 return mDeviceFeaturesMap.get(device); 742 } 743 return 0x00; 744 } 745 stackEventPresetInfoReasonToProfileStatus(int statusCode)746 private int stackEventPresetInfoReasonToProfileStatus(int statusCode) { 747 switch (statusCode) { 748 case HapClientStackEvent.PRESET_INFO_REASON_ALL_PRESET_INFO: 749 return BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST; 750 case HapClientStackEvent.PRESET_INFO_REASON_PRESET_INFO_UPDATE: 751 return BluetoothStatusCodes.REASON_REMOTE_REQUEST; 752 case HapClientStackEvent.PRESET_INFO_REASON_PRESET_DELETED: 753 return BluetoothStatusCodes.REASON_REMOTE_REQUEST; 754 case HapClientStackEvent.PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED: 755 return BluetoothStatusCodes.REASON_REMOTE_REQUEST; 756 case HapClientStackEvent.PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE: 757 return BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST; 758 default: 759 return BluetoothStatusCodes.ERROR_UNKNOWN; 760 } 761 } 762 notifyPresetInfoChanged(BluetoothDevice device, int infoReason)763 private void notifyPresetInfoChanged(BluetoothDevice device, int infoReason) { 764 List current_presets = mPresetsMap.get(device); 765 if (current_presets == null) return; 766 767 if (mCallbacks != null) { 768 int n = mCallbacks.beginBroadcast(); 769 for (int i = 0; i < n; i++) { 770 try { 771 mCallbacks.getBroadcastItem(i).onPresetInfoChanged(device, current_presets, 772 stackEventPresetInfoReasonToProfileStatus(infoReason)); 773 } catch (RemoteException e) { 774 continue; 775 } 776 } 777 mCallbacks.finishBroadcast(); 778 } 779 } 780 notifyPresetInfoForGroupChanged(int groupId, int infoReason)781 private void notifyPresetInfoForGroupChanged(int groupId, int infoReason) { 782 List<BluetoothDevice> all_group_devices = getGroupDevices(groupId); 783 for (BluetoothDevice dev : all_group_devices) { 784 notifyPresetInfoChanged(dev, infoReason); 785 } 786 } 787 notifyFeaturesAvailable(BluetoothDevice device, int features)788 private void notifyFeaturesAvailable(BluetoothDevice device, int features) { 789 Log.d(TAG, "HAP device: " + device + ", features: " + String.format("0x%04X", features)); 790 } 791 notifyActivePresetChanged(BluetoothDevice device, int presetIndex, int reasonCode)792 private void notifyActivePresetChanged(BluetoothDevice device, int presetIndex, 793 int reasonCode) { 794 if (mCallbacks != null) { 795 int n = mCallbacks.beginBroadcast(); 796 for (int i = 0; i < n; i++) { 797 try { 798 mCallbacks.getBroadcastItem(i).onPresetSelected(device, presetIndex, 799 reasonCode); 800 } catch (RemoteException e) { 801 continue; 802 } 803 } 804 mCallbacks.finishBroadcast(); 805 } 806 } 807 notifyActivePresetChangedForGroup(int groupId, int presetIndex, int reasonCode)808 private void notifyActivePresetChangedForGroup(int groupId, int presetIndex, int reasonCode) { 809 List<BluetoothDevice> all_group_devices = getGroupDevices(groupId); 810 for (BluetoothDevice dev : all_group_devices) { 811 notifyActivePresetChanged(dev, presetIndex, reasonCode); 812 } 813 } 814 stackEventStatusToProfileStatus(int statusCode)815 private int stackEventStatusToProfileStatus(int statusCode) { 816 switch (statusCode) { 817 case HapClientStackEvent.STATUS_SET_NAME_NOT_ALLOWED: 818 return BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED; 819 case HapClientStackEvent.STATUS_OPERATION_NOT_SUPPORTED: 820 return BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED; 821 case HapClientStackEvent.STATUS_OPERATION_NOT_POSSIBLE: 822 return BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED; 823 case HapClientStackEvent.STATUS_INVALID_PRESET_NAME_LENGTH: 824 return BluetoothStatusCodes.ERROR_HAP_PRESET_NAME_TOO_LONG; 825 case HapClientStackEvent.STATUS_INVALID_PRESET_INDEX: 826 return BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX; 827 case HapClientStackEvent.STATUS_GROUP_OPERATION_NOT_SUPPORTED: 828 return BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED; 829 case HapClientStackEvent.STATUS_PROCEDURE_ALREADY_IN_PROGRESS: 830 return BluetoothStatusCodes.ERROR_UNKNOWN; 831 default: 832 return BluetoothStatusCodes.ERROR_UNKNOWN; 833 } 834 } 835 notifySelectActivePresetFailed(BluetoothDevice device, int statusCode)836 private void notifySelectActivePresetFailed(BluetoothDevice device, int statusCode) { 837 if (mCallbacks != null) { 838 int n = mCallbacks.beginBroadcast(); 839 for (int i = 0; i < n; i++) { 840 try { 841 mCallbacks.getBroadcastItem(i).onPresetSelectionFailed(device, 842 stackEventStatusToProfileStatus(statusCode)); 843 } catch (RemoteException e) { 844 continue; 845 } 846 } 847 mCallbacks.finishBroadcast(); 848 } 849 } 850 notifySelectActivePresetForGroupFailed(int groupId, int statusCode)851 private void notifySelectActivePresetForGroupFailed(int groupId, int statusCode) { 852 if (mCallbacks != null) { 853 int n = mCallbacks.beginBroadcast(); 854 for (int i = 0; i < n; i++) { 855 try { 856 mCallbacks.getBroadcastItem(i).onPresetSelectionForGroupFailed(groupId, 857 stackEventStatusToProfileStatus(statusCode)); 858 } catch (RemoteException e) { 859 continue; 860 } 861 } 862 mCallbacks.finishBroadcast(); 863 } 864 } 865 notifySetPresetNameFailed(BluetoothDevice device, int statusCode)866 private void notifySetPresetNameFailed(BluetoothDevice device, int statusCode) { 867 if (mCallbacks != null) { 868 int n = mCallbacks.beginBroadcast(); 869 for (int i = 0; i < n; i++) { 870 try { 871 mCallbacks.getBroadcastItem(i).onSetPresetNameFailed(device, 872 stackEventStatusToProfileStatus(statusCode)); 873 } catch (RemoteException e) { 874 continue; 875 } 876 } 877 mCallbacks.finishBroadcast(); 878 } 879 } 880 notifySetPresetNameForGroupFailed(int groupId, int statusCode)881 private void notifySetPresetNameForGroupFailed(int groupId, int statusCode) { 882 if (mCallbacks != null) { 883 int n = mCallbacks.beginBroadcast(); 884 for (int i = 0; i < n; i++) { 885 try { 886 mCallbacks.getBroadcastItem(i).onSetPresetNameForGroupFailed(groupId, 887 stackEventStatusToProfileStatus(statusCode)); 888 } catch (RemoteException e) { 889 continue; 890 } 891 } 892 mCallbacks.finishBroadcast(); 893 } 894 } 895 isPresetIndexValid(BluetoothDevice device, int presetIndex)896 private boolean isPresetIndexValid(BluetoothDevice device, int presetIndex) { 897 if (presetIndex == BluetoothHapClient.PRESET_INDEX_UNAVAILABLE) return false; 898 899 List<BluetoothHapPresetInfo> device_presets = mPresetsMap.get(device); 900 if (device_presets != null) { 901 for (BluetoothHapPresetInfo preset : device_presets) { 902 if (preset.getIndex() == presetIndex) { 903 return true; 904 } 905 } 906 } 907 return false; 908 } 909 isPresetIndexValid(int groupId, int presetIndex)910 private boolean isPresetIndexValid(int groupId, int presetIndex) { 911 List<BluetoothDevice> all_group_devices = getGroupDevices(groupId); 912 if (all_group_devices.isEmpty()) return false; 913 914 for (BluetoothDevice device : all_group_devices) { 915 if (!isPresetIndexValid(device, presetIndex)) return false; 916 } 917 return true; 918 } 919 920 isGroupIdValid(int groupId)921 private boolean isGroupIdValid(int groupId) { 922 if (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) return false; 923 924 CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService(); 925 if (csipClient != null) { 926 List<Integer> groups = csipClient.getAllGroupIds(BluetoothUuid.CAP); 927 return groups.contains(groupId); 928 } 929 return false; 930 } 931 932 /** 933 * Sets the preset name 934 * 935 * @param device is the device for which we want to get the preset name 936 * @param presetIndex is an index of one of the available presets 937 * @param name is a new name for a preset 938 */ setPresetName(BluetoothDevice device, int presetIndex, String name)939 public void setPresetName(BluetoothDevice device, int presetIndex, String name) { 940 if (!isPresetIndexValid(device, presetIndex)) { 941 if (mCallbacks != null) { 942 int n = mCallbacks.beginBroadcast(); 943 for (int i = 0; i < n; i++) { 944 try { 945 mCallbacks.getBroadcastItem(i).onSetPresetNameFailed(device, 946 BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX); 947 } catch (RemoteException e) { 948 continue; 949 } 950 } 951 mCallbacks.finishBroadcast(); 952 } 953 return; 954 } 955 // WARNING: We should check cache if preset exists and is writable, but then we would still 956 // need a way to trigger this action with an invalid index or on a non-writable 957 // preset for tests purpose. 958 mHapClientNativeInterface.setPresetName(device, presetIndex, name); 959 } 960 961 /** 962 * Sets the preset name 963 * 964 * @param groupId is the device group identifier 965 * @param presetIndex is an index of one of the available presets 966 * @param name is a new name for a preset 967 */ setPresetNameForGroup(int groupId, int presetIndex, String name)968 public void setPresetNameForGroup(int groupId, int presetIndex, String name) { 969 int status = BluetoothStatusCodes.SUCCESS; 970 971 if (!isGroupIdValid(groupId)) { 972 status = BluetoothStatusCodes.ERROR_CSIP_INVALID_GROUP_ID; 973 } else if (!isPresetIndexValid(groupId, presetIndex)) { 974 status = BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX; 975 } 976 if (status != BluetoothStatusCodes.SUCCESS) { 977 if (mCallbacks != null) { 978 int n = mCallbacks.beginBroadcast(); 979 for (int i = 0; i < n; i++) { 980 try { 981 mCallbacks.getBroadcastItem(i).onSetPresetNameForGroupFailed(groupId, 982 status); 983 } catch (RemoteException e) { 984 continue; 985 } 986 } 987 mCallbacks.finishBroadcast(); 988 } 989 return; 990 } 991 992 mHapClientNativeInterface.groupSetPresetName(groupId, presetIndex, name); 993 } 994 995 @Override dump(StringBuilder sb)996 public void dump(StringBuilder sb) { 997 super.dump(sb); 998 for (HapClientStateMachine sm : mStateMachines.values()) { 999 sm.dump(sb); 1000 } 1001 } 1002 isPresetCoordinationSupported(BluetoothDevice device)1003 private boolean isPresetCoordinationSupported(BluetoothDevice device) { 1004 Integer features = mDeviceFeaturesMap.getOrDefault(device, 0x00); 1005 return BigInteger.valueOf(features).testBit( 1006 HapClientStackEvent.FEATURE_BIT_NUM_SYNCHRONIZATED_PRESETS); 1007 } 1008 updateDevicePresetsCache(BluetoothDevice device, int infoReason, List<BluetoothHapPresetInfo> presets)1009 void updateDevicePresetsCache(BluetoothDevice device, int infoReason, 1010 List<BluetoothHapPresetInfo> presets) { 1011 switch (infoReason) { 1012 case HapClientStackEvent.PRESET_INFO_REASON_ALL_PRESET_INFO: 1013 mPresetsMap.put(device, presets); 1014 break; 1015 case HapClientStackEvent.PRESET_INFO_REASON_PRESET_INFO_UPDATE: 1016 case HapClientStackEvent.PRESET_INFO_REASON_PRESET_AVAILABILITY_CHANGED: 1017 case HapClientStackEvent.PRESET_INFO_REASON_PRESET_INFO_REQUEST_RESPONSE: { 1018 List current_presets = mPresetsMap.get(device); 1019 if (current_presets != null) { 1020 ListIterator<BluetoothHapPresetInfo> iter = current_presets.listIterator(); 1021 for (BluetoothHapPresetInfo new_preset : presets) { 1022 while (iter.hasNext()) { 1023 if (iter.next().getIndex() == new_preset.getIndex()) { 1024 iter.remove(); 1025 } 1026 } 1027 } 1028 current_presets.addAll(presets); 1029 mPresetsMap.put(device, current_presets); 1030 } else { 1031 mPresetsMap.put(device, presets); 1032 } 1033 } 1034 break; 1035 1036 case HapClientStackEvent.PRESET_INFO_REASON_PRESET_DELETED: { 1037 List current_presets = mPresetsMap.get(device); 1038 if (current_presets != null) { 1039 ListIterator<BluetoothHapPresetInfo> iter = current_presets.listIterator(); 1040 for (BluetoothHapPresetInfo new_preset : presets) { 1041 while (iter.hasNext()) { 1042 if (iter.next().getIndex() == new_preset.getIndex()) { 1043 iter.remove(); 1044 } 1045 } 1046 } 1047 mPresetsMap.put(device, current_presets); 1048 } 1049 } 1050 break; 1051 1052 default: 1053 break; 1054 } 1055 } 1056 getGroupDevices(int groupId)1057 private List<BluetoothDevice> getGroupDevices(int groupId) { 1058 List<BluetoothDevice> devices = new ArrayList<>(); 1059 1060 CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService(); 1061 if (csipClient != null) { 1062 if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) { 1063 devices = csipClient.getGroupDevicesOrdered(groupId); 1064 } 1065 } 1066 return devices; 1067 } 1068 1069 /** 1070 * Handle messages from native (JNI) to Java 1071 * 1072 * @param stackEvent the event that need to be handled 1073 */ messageFromNative(HapClientStackEvent stackEvent)1074 public void messageFromNative(HapClientStackEvent stackEvent) { 1075 // Decide which event should be sent to the state machine 1076 if (stackEvent.type == HapClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { 1077 resendToStateMachine(stackEvent); 1078 return; 1079 } 1080 1081 Intent intent = null; 1082 BluetoothDevice device = stackEvent.device; 1083 1084 switch (stackEvent.type) { 1085 case (HapClientStackEvent.EVENT_TYPE_DEVICE_AVAILABLE): { 1086 int features = stackEvent.valueInt1; 1087 1088 if (device != null) { 1089 mDeviceFeaturesMap.put(device, features); 1090 1091 intent = new Intent(BluetoothHapClient.ACTION_HAP_DEVICE_AVAILABLE); 1092 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 1093 intent.putExtra(BluetoothHapClient.EXTRA_HAP_FEATURES, features); 1094 } 1095 } break; 1096 1097 case (HapClientStackEvent.EVENT_TYPE_DEVICE_FEATURES): { 1098 int features = stackEvent.valueInt1; 1099 1100 if (device != null) { 1101 mDeviceFeaturesMap.put(device, features); 1102 notifyFeaturesAvailable(device, features); 1103 } 1104 } return; 1105 1106 case (HapClientStackEvent.EVENT_TYPE_ON_ACTIVE_PRESET_SELECTED): { 1107 int currentPresetIndex = stackEvent.valueInt1; 1108 int groupId = stackEvent.valueInt2; 1109 1110 if (device != null) { 1111 mDeviceCurrentPresetMap.put(device, currentPresetIndex); 1112 // FIXME: Add app request queueing to support other reasons 1113 int reasonCode = BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST; 1114 notifyActivePresetChanged(device, currentPresetIndex, reasonCode); 1115 1116 } else if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { 1117 List<BluetoothDevice> all_group_devices = getGroupDevices(groupId); 1118 for (BluetoothDevice dev : all_group_devices) { 1119 mDeviceCurrentPresetMap.put(dev, currentPresetIndex); 1120 } 1121 // FIXME: Add app request queueing to support other reasons 1122 int reasonCode = BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST; 1123 notifyActivePresetChangedForGroup(groupId, currentPresetIndex, reasonCode); 1124 } 1125 } return; 1126 1127 case (HapClientStackEvent.EVENT_TYPE_ON_ACTIVE_PRESET_SELECT_ERROR): { 1128 int groupId = stackEvent.valueInt2; 1129 int statusCode = stackEvent.valueInt1; 1130 1131 if (device != null) { 1132 notifySelectActivePresetFailed(device, statusCode); 1133 } else if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { 1134 notifySelectActivePresetForGroupFailed(groupId, statusCode); 1135 } 1136 } break; 1137 1138 case (HapClientStackEvent.EVENT_TYPE_ON_PRESET_INFO): { 1139 int presetIndex = stackEvent.valueInt1; 1140 int infoReason = stackEvent.valueInt2; 1141 int groupId = stackEvent.valueInt3; 1142 ArrayList presets = stackEvent.valueList; 1143 1144 if (device != null) { 1145 updateDevicePresetsCache(device, infoReason, presets); 1146 notifyPresetInfoChanged(device, infoReason); 1147 1148 } else if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { 1149 List<BluetoothDevice> all_group_devices = getGroupDevices(groupId); 1150 for (BluetoothDevice dev : all_group_devices) { 1151 updateDevicePresetsCache(dev, infoReason, presets); 1152 } 1153 notifyPresetInfoForGroupChanged(groupId, infoReason); 1154 } 1155 1156 } return; 1157 1158 case (HapClientStackEvent.EVENT_TYPE_ON_PRESET_NAME_SET_ERROR): { 1159 int statusCode = stackEvent.valueInt1; 1160 int presetIndex = stackEvent.valueInt2; 1161 int groupId = stackEvent.valueInt3; 1162 1163 if (device != null) { 1164 notifySetPresetNameFailed(device, statusCode); 1165 } else if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { 1166 notifySetPresetNameForGroupFailed(groupId, statusCode); 1167 } 1168 } break; 1169 1170 case (HapClientStackEvent.EVENT_TYPE_ON_PRESET_INFO_ERROR): { 1171 // Used only to report back on hidden API calls used for testing. 1172 Log.d(TAG, stackEvent.toString()); 1173 } break; 1174 1175 default: 1176 return; 1177 } 1178 1179 if (intent != null) { 1180 sendBroadcast(intent, BLUETOOTH_PRIVILEGED); 1181 } 1182 } 1183 resendToStateMachine(HapClientStackEvent stackEvent)1184 private void resendToStateMachine(HapClientStackEvent stackEvent) { 1185 synchronized (mStateMachines) { 1186 BluetoothDevice device = stackEvent.device; 1187 HapClientStateMachine sm = mStateMachines.get(device); 1188 1189 if (sm == null) { 1190 if (stackEvent.type == HapClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { 1191 switch (stackEvent.valueInt1) { 1192 case HapClientStackEvent.CONNECTION_STATE_CONNECTED: 1193 case HapClientStackEvent.CONNECTION_STATE_CONNECTING: 1194 sm = getOrCreateStateMachine(device); 1195 break; 1196 default: 1197 break; 1198 } 1199 } 1200 } 1201 if (sm == null) { 1202 Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); 1203 return; 1204 } 1205 sm.sendMessage(HapClientStateMachine.STACK_EVENT, stackEvent); 1206 } 1207 } 1208 1209 /** 1210 * Binder object: must be a static class or memory leak may occur 1211 */ 1212 @VisibleForTesting 1213 static class BluetoothHapClientBinder extends IBluetoothHapClient.Stub 1214 implements IProfileServiceBinder { 1215 @VisibleForTesting 1216 boolean mIsTesting = false; 1217 private HapClientService mService; 1218 BluetoothHapClientBinder(HapClientService svc)1219 BluetoothHapClientBinder(HapClientService svc) { 1220 mService = svc; 1221 } 1222 getService(AttributionSource source)1223 private HapClientService getService(AttributionSource source) { 1224 if (mIsTesting) { 1225 return mService; 1226 } 1227 if (!Utils.checkServiceAvailable(mService, TAG) 1228 || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG) 1229 || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) { 1230 Log.w(TAG, "Hearing Access call not allowed for non-active user"); 1231 return null; 1232 } 1233 1234 if (mService != null && mService.isAvailable()) { 1235 return mService; 1236 } 1237 return null; 1238 } 1239 1240 @Override cleanup()1241 public void cleanup() { 1242 mService = null; 1243 } 1244 1245 @Override getConnectedDevices(AttributionSource source, SynchronousResultReceiver receiver)1246 public void getConnectedDevices(AttributionSource source, 1247 SynchronousResultReceiver receiver) { 1248 try { 1249 Objects.requireNonNull(source, "source cannot be null"); 1250 Objects.requireNonNull(receiver, "receiver cannot be null"); 1251 List<BluetoothDevice> defaultValue = new ArrayList<>(); 1252 HapClientService service = getService(source); 1253 if (service == null) { 1254 throw new IllegalStateException("service is null"); 1255 } 1256 enforceBluetoothPrivilegedPermission(service); 1257 defaultValue = service.getConnectedDevices(); 1258 receiver.send(defaultValue); 1259 } catch (RuntimeException e) { 1260 receiver.propagateException(e); 1261 } 1262 } 1263 1264 @Override getDevicesMatchingConnectionStates(int[] states, AttributionSource source, SynchronousResultReceiver receiver)1265 public void getDevicesMatchingConnectionStates(int[] states, 1266 AttributionSource source, SynchronousResultReceiver receiver) { 1267 try { 1268 Objects.requireNonNull(source, "source cannot be null"); 1269 Objects.requireNonNull(receiver, "receiver cannot be null"); 1270 List<BluetoothDevice> defaultValue = new ArrayList<>(); 1271 HapClientService service = getService(source); 1272 if (service == null) { 1273 throw new IllegalStateException("service is null"); 1274 } 1275 enforceBluetoothPrivilegedPermission(service); 1276 defaultValue = service.getDevicesMatchingConnectionStates(states); 1277 receiver.send(defaultValue); 1278 } catch (RuntimeException e) { 1279 receiver.propagateException(e); 1280 } 1281 } 1282 1283 @Override getConnectionState(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1284 public void getConnectionState(BluetoothDevice device, AttributionSource source, 1285 SynchronousResultReceiver receiver) { 1286 try { 1287 Objects.requireNonNull(device, "device cannot be null"); 1288 Objects.requireNonNull(source, "source cannot be null"); 1289 Objects.requireNonNull(receiver, "receiver cannot be null"); 1290 int defaultValue = BluetoothProfile.STATE_DISCONNECTED; 1291 HapClientService service = getService(source); 1292 if (service == null) { 1293 throw new IllegalStateException("service is null"); 1294 } 1295 enforceBluetoothPrivilegedPermission(service); 1296 defaultValue = service.getConnectionState(device); 1297 receiver.send(defaultValue); 1298 } catch (RuntimeException e) { 1299 receiver.propagateException(e); 1300 } 1301 } 1302 1303 @Override setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source, SynchronousResultReceiver receiver)1304 public void setConnectionPolicy(BluetoothDevice device, int connectionPolicy, 1305 AttributionSource source, SynchronousResultReceiver receiver) { 1306 try { 1307 Objects.requireNonNull(device, "device cannot be null"); 1308 Objects.requireNonNull(source, "source cannot be null"); 1309 Objects.requireNonNull(receiver, "receiver cannot be null"); 1310 boolean defaultValue = false; 1311 HapClientService service = getService(source); 1312 if (service == null) { 1313 throw new IllegalStateException("service is null"); 1314 } 1315 enforceBluetoothPrivilegedPermission(service); 1316 defaultValue = service.setConnectionPolicy(device, connectionPolicy); 1317 receiver.send(defaultValue); 1318 } catch (RuntimeException e) { 1319 receiver.propagateException(e); 1320 } 1321 } 1322 1323 @Override getConnectionPolicy(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1324 public void getConnectionPolicy(BluetoothDevice device, AttributionSource source, 1325 SynchronousResultReceiver receiver) { 1326 try { 1327 Objects.requireNonNull(device, "device cannot be null"); 1328 Objects.requireNonNull(source, "source cannot be null"); 1329 Objects.requireNonNull(receiver, "receiver cannot be null"); 1330 int defaultValue = BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 1331 HapClientService service = getService(source); 1332 if (service == null) { 1333 throw new IllegalStateException("service is null"); 1334 } 1335 enforceBluetoothPrivilegedPermission(service); 1336 defaultValue = service.getConnectionPolicy(device); 1337 receiver.send(defaultValue); 1338 } catch (RuntimeException e) { 1339 receiver.propagateException(e); 1340 } 1341 } 1342 1343 @Override getActivePresetIndex(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1344 public void getActivePresetIndex(BluetoothDevice device, AttributionSource source, 1345 SynchronousResultReceiver receiver) { 1346 try { 1347 Objects.requireNonNull(device, "device cannot be null"); 1348 Objects.requireNonNull(source, "source cannot be null"); 1349 Objects.requireNonNull(receiver, "receiver cannot be null"); 1350 int defaultValue = BluetoothHapClient.PRESET_INDEX_UNAVAILABLE; 1351 HapClientService service = getService(source); 1352 if (service == null) { 1353 throw new IllegalStateException("service is null"); 1354 } 1355 enforceBluetoothPrivilegedPermission(service); 1356 defaultValue = service.getActivePresetIndex(device); 1357 receiver.send(defaultValue); 1358 } catch (RuntimeException e) { 1359 receiver.propagateException(e); 1360 } 1361 } 1362 1363 @Override getActivePresetInfo(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1364 public void getActivePresetInfo(BluetoothDevice device, 1365 AttributionSource source, SynchronousResultReceiver receiver) { 1366 try { 1367 Objects.requireNonNull(device, "device cannot be null"); 1368 Objects.requireNonNull(source, "source cannot be null"); 1369 Objects.requireNonNull(receiver, "receiver cannot be null"); 1370 BluetoothHapPresetInfo defaultValue = null; 1371 HapClientService service = getService(source); 1372 if (service == null) { 1373 throw new IllegalStateException("service is null"); 1374 } 1375 enforceBluetoothPrivilegedPermission(service); 1376 defaultValue = service.getActivePresetInfo(device); 1377 receiver.send(defaultValue); 1378 } catch (RuntimeException e) { 1379 receiver.propagateException(e); 1380 } 1381 } 1382 1383 @Override getHapGroup(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1384 public void getHapGroup(BluetoothDevice device, AttributionSource source, 1385 SynchronousResultReceiver receiver) { 1386 try { 1387 Objects.requireNonNull(device, "device cannot be null"); 1388 Objects.requireNonNull(source, "source cannot be null"); 1389 Objects.requireNonNull(receiver, "receiver cannot be null"); 1390 int defaultValue = BluetoothCsipSetCoordinator.GROUP_ID_INVALID; 1391 HapClientService service = getService(source); 1392 if (service == null) { 1393 throw new IllegalStateException("service is null"); 1394 } 1395 enforceBluetoothPrivilegedPermission(service); 1396 defaultValue = service.getHapGroup(device); 1397 receiver.send(defaultValue); 1398 } catch (RuntimeException e) { 1399 receiver.propagateException(e); 1400 } 1401 } 1402 1403 @Override selectPreset(BluetoothDevice device, int presetIndex, AttributionSource source)1404 public void selectPreset(BluetoothDevice device, int presetIndex, 1405 AttributionSource source) { 1406 if (source == null) { 1407 Log.w(TAG, "source cannot be null"); 1408 return; 1409 } 1410 1411 HapClientService service = getService(source); 1412 if (service == null) { 1413 Log.w(TAG, "service is null"); 1414 return; 1415 } 1416 enforceBluetoothPrivilegedPermission(service); 1417 service.selectPreset(device, presetIndex); 1418 } 1419 1420 @Override selectPresetForGroup(int groupId, int presetIndex, AttributionSource source)1421 public void selectPresetForGroup(int groupId, int presetIndex, AttributionSource source) { 1422 if (source == null) { 1423 Log.w(TAG, "source cannot be null"); 1424 return; 1425 } 1426 1427 HapClientService service = getService(source); 1428 if (service == null) { 1429 Log.w(TAG, "service is null"); 1430 return; 1431 } 1432 enforceBluetoothPrivilegedPermission(service); 1433 service.selectPresetForGroup(groupId, presetIndex); 1434 } 1435 1436 @Override switchToNextPreset(BluetoothDevice device, AttributionSource source)1437 public void switchToNextPreset(BluetoothDevice device, AttributionSource source) { 1438 if (source == null) { 1439 Log.w(TAG, "source cannot be null"); 1440 return; 1441 } 1442 1443 HapClientService service = getService(source); 1444 if (service == null) { 1445 Log.w(TAG, "service is null"); 1446 return; 1447 } 1448 enforceBluetoothPrivilegedPermission(service); 1449 service.switchToNextPreset(device); 1450 } 1451 1452 @Override switchToNextPresetForGroup(int groupId, AttributionSource source)1453 public void switchToNextPresetForGroup(int groupId, AttributionSource source) { 1454 if (source == null) { 1455 Log.w(TAG, "source cannot be null"); 1456 return; 1457 } 1458 1459 HapClientService service = getService(source); 1460 if (service == null) { 1461 Log.w(TAG, "service is null"); 1462 return; 1463 } 1464 enforceBluetoothPrivilegedPermission(service); 1465 service.switchToNextPresetForGroup(groupId); 1466 } 1467 1468 @Override switchToPreviousPreset(BluetoothDevice device, AttributionSource source)1469 public void switchToPreviousPreset(BluetoothDevice device, AttributionSource source) { 1470 if (source == null) { 1471 Log.w(TAG, "source cannot be null"); 1472 return; 1473 } 1474 1475 HapClientService service = getService(source); 1476 if (service == null) { 1477 Log.w(TAG, "service is null"); 1478 return; 1479 } 1480 enforceBluetoothPrivilegedPermission(service); 1481 service.switchToPreviousPreset(device); 1482 } 1483 1484 @Override switchToPreviousPresetForGroup(int groupId, AttributionSource source)1485 public void switchToPreviousPresetForGroup(int groupId, AttributionSource source) { 1486 if (source == null) { 1487 Log.w(TAG, "source cannot be null"); 1488 return; 1489 } 1490 1491 HapClientService service = getService(source); 1492 if (service == null) { 1493 Log.w(TAG, "service is null"); 1494 return; 1495 } 1496 enforceBluetoothPrivilegedPermission(service); 1497 service.switchToPreviousPresetForGroup(groupId); 1498 } 1499 1500 @Override getPresetInfo(BluetoothDevice device, int presetIndex, AttributionSource source, SynchronousResultReceiver receiver)1501 public void getPresetInfo(BluetoothDevice device, int presetIndex, 1502 AttributionSource source, SynchronousResultReceiver receiver) { 1503 1504 try { 1505 Objects.requireNonNull(device, "device cannot be null"); 1506 Objects.requireNonNull(source, "source cannot be null"); 1507 Objects.requireNonNull(receiver, "receiver cannot be null"); 1508 BluetoothHapPresetInfo defaultValue = null; 1509 HapClientService service = getService(source); 1510 if (service == null) { 1511 throw new IllegalStateException("service is null"); 1512 } 1513 enforceBluetoothPrivilegedPermission(service); 1514 defaultValue = service.getPresetInfo(device, presetIndex); 1515 receiver.send(defaultValue); 1516 } catch (RuntimeException e) { 1517 receiver.propagateException(e); 1518 } 1519 } 1520 1521 @Override getAllPresetInfo(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1522 public void getAllPresetInfo(BluetoothDevice device, AttributionSource source, 1523 SynchronousResultReceiver receiver) { 1524 try { 1525 Objects.requireNonNull(device, "device cannot be null"); 1526 Objects.requireNonNull(source, "source cannot be null"); 1527 Objects.requireNonNull(receiver, "receiver cannot be null"); 1528 List<BluetoothHapPresetInfo> defaultValue = new ArrayList<>(); 1529 HapClientService service = getService(source); 1530 if (service == null) { 1531 throw new IllegalStateException("service is null"); 1532 } 1533 enforceBluetoothPrivilegedPermission(service); 1534 defaultValue = service.getAllPresetInfo(device); 1535 receiver.send(defaultValue); 1536 } catch (RuntimeException e) { 1537 receiver.propagateException(e); 1538 } 1539 } 1540 1541 @Override getFeatures(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1542 public void getFeatures(BluetoothDevice device, AttributionSource source, 1543 SynchronousResultReceiver receiver) { 1544 try { 1545 Objects.requireNonNull(device, "device cannot be null"); 1546 Objects.requireNonNull(source, "source cannot be null"); 1547 Objects.requireNonNull(receiver, "receiver cannot be null"); 1548 int defaultValue = 0x00; 1549 HapClientService service = getService(source); 1550 if (service == null) { 1551 throw new IllegalStateException("service is null"); 1552 } 1553 enforceBluetoothPrivilegedPermission(service); 1554 defaultValue = service.getFeatures(device); 1555 receiver.send(defaultValue); 1556 } catch (RuntimeException e) { 1557 receiver.propagateException(e); 1558 } 1559 } 1560 1561 @Override setPresetName(BluetoothDevice device, int presetIndex, String name, AttributionSource source)1562 public void setPresetName(BluetoothDevice device, int presetIndex, String name, 1563 AttributionSource source) { 1564 if (device == null) { 1565 Log.w(TAG, "device cannot be null"); 1566 return; 1567 } 1568 if (name == null) { 1569 Log.w(TAG, "name cannot be null"); 1570 return; 1571 } 1572 if (source == null) { 1573 Log.w(TAG, "source cannot be null"); 1574 return; 1575 } 1576 1577 HapClientService service = getService(source); 1578 if (service == null) { 1579 Log.w(TAG, "service is null"); 1580 return; 1581 } 1582 enforceBluetoothPrivilegedPermission(service); 1583 service.setPresetName(device, presetIndex, name); 1584 } 1585 1586 @Override setPresetNameForGroup(int groupId, int presetIndex, String name, AttributionSource source)1587 public void setPresetNameForGroup(int groupId, int presetIndex, String name, 1588 AttributionSource source) { 1589 if (name == null) { 1590 Log.w(TAG, "name cannot be null"); 1591 return; 1592 } 1593 if (source == null) { 1594 Log.w(TAG, "source cannot be null"); 1595 return; 1596 } 1597 HapClientService service = getService(source); 1598 if (service == null) { 1599 Log.w(TAG, "service is null"); 1600 return; 1601 } 1602 enforceBluetoothPrivilegedPermission(service); 1603 service.setPresetNameForGroup(groupId, presetIndex, name); 1604 } 1605 1606 @Override registerCallback(IBluetoothHapClientCallback callback, AttributionSource source, SynchronousResultReceiver receiver)1607 public void registerCallback(IBluetoothHapClientCallback callback, 1608 AttributionSource source, SynchronousResultReceiver receiver) { 1609 try { 1610 Objects.requireNonNull(callback, "callback cannot be null"); 1611 Objects.requireNonNull(source, "source cannot be null"); 1612 Objects.requireNonNull(receiver, "receiver cannot be null"); 1613 HapClientService service = getService(source); 1614 if (service == null) { 1615 throw new IllegalStateException("Service is unavailable"); 1616 } 1617 enforceBluetoothPrivilegedPermission(service); 1618 service.mCallbacks.register(callback); 1619 receiver.send(null); 1620 } catch (RuntimeException e) { 1621 receiver.propagateException(e); 1622 } 1623 } 1624 1625 @Override unregisterCallback(IBluetoothHapClientCallback callback, AttributionSource source, SynchronousResultReceiver receiver)1626 public void unregisterCallback(IBluetoothHapClientCallback callback, 1627 AttributionSource source, SynchronousResultReceiver receiver) { 1628 try { 1629 Objects.requireNonNull(callback, "callback cannot be null"); 1630 Objects.requireNonNull(source, "source cannot be null"); 1631 Objects.requireNonNull(receiver, "receiver cannot be null"); 1632 1633 HapClientService service = getService(source); 1634 if (service == null) { 1635 throw new IllegalStateException("Service is unavailable"); 1636 } 1637 enforceBluetoothPrivilegedPermission(service); 1638 service.mCallbacks.unregister(callback); 1639 receiver.send(null); 1640 } catch (RuntimeException e) { 1641 receiver.propagateException(e); 1642 } 1643 } 1644 } 1645 1646 // Remove state machine if the bonding for a device is removed 1647 private class BondStateChangedReceiver extends BroadcastReceiver { 1648 @Override onReceive(Context context, Intent intent)1649 public void onReceive(Context context, Intent intent) { 1650 if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { 1651 return; 1652 } 1653 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 1654 BluetoothDevice.ERROR); 1655 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 1656 Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); 1657 bondStateChanged(device, state); 1658 } 1659 } 1660 1661 private class ConnectionStateChangedReceiver extends BroadcastReceiver { 1662 @Override onReceive(Context context, Intent intent)1663 public void onReceive(Context context, Intent intent) { 1664 if (!BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED.equals( 1665 intent.getAction())) { 1666 return; 1667 } 1668 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 1669 int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 1670 int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 1671 connectionStateChanged(device, fromState, toState); 1672 } 1673 } 1674 } 1675