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 android.bluetooth; 19 20 import android.annotation.CallbackExecutor; 21 import android.annotation.FlaggedApi; 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.RequiresPermission; 26 import android.annotation.SdkConstant; 27 import android.annotation.SuppressLint; 28 import android.annotation.SystemApi; 29 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 30 import android.content.AttributionSource; 31 import android.content.Context; 32 import android.os.IBinder; 33 import android.os.RemoteException; 34 import android.util.CloseGuard; 35 import android.util.Log; 36 37 import com.android.bluetooth.flags.Flags; 38 39 import java.lang.annotation.Retention; 40 import java.lang.annotation.RetentionPolicy; 41 import java.util.Collections; 42 import java.util.HashMap; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.Objects; 46 import java.util.concurrent.Executor; 47 48 /** 49 * This class provides a public APIs to control the Bluetooth Hearing Access Profile client service. 50 * 51 * <p>BluetoothHapClient is a proxy object for controlling the Bluetooth HAP Service client via IPC. 52 * Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothHapClient proxy object. 53 * 54 * @hide 55 */ 56 @SystemApi 57 public final class BluetoothHapClient implements BluetoothProfile, AutoCloseable { 58 private static final String TAG = "BluetoothHapClient"; 59 private static final boolean DBG = false; 60 private static final boolean VDBG = false; 61 62 private final Map<Callback, Executor> mCallbackExecutorMap = new HashMap<>(); 63 64 private CloseGuard mCloseGuard; 65 66 /** 67 * This class provides callbacks mechanism for the BluetoothHapClient profile. 68 * 69 * @hide 70 */ 71 @SystemApi 72 public interface Callback { 73 /** @hide */ 74 @Retention(RetentionPolicy.SOURCE) 75 @IntDef( 76 value = { 77 // needed for future release compatibility 78 BluetoothStatusCodes.ERROR_UNKNOWN, 79 BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST, 80 BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST, 81 BluetoothStatusCodes.REASON_REMOTE_REQUEST, 82 BluetoothStatusCodes.REASON_SYSTEM_POLICY, 83 }) 84 @interface PresetSelectionReason {} 85 86 /** 87 * Invoked to inform about HA device's currently active preset. 88 * 89 * @param device remote device, 90 * @param presetIndex the currently active preset index. 91 * @param reason reason for the selected preset change 92 * @hide 93 */ 94 @SystemApi onPresetSelected( @onNull BluetoothDevice device, int presetIndex, @PresetSelectionReason int reason)95 void onPresetSelected( 96 @NonNull BluetoothDevice device, 97 int presetIndex, 98 @PresetSelectionReason int reason); 99 100 /** @hide */ 101 @Retention(RetentionPolicy.SOURCE) 102 @IntDef( 103 value = { 104 // needed for future release compatibility 105 BluetoothStatusCodes.ERROR_UNKNOWN, 106 BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST, 107 BluetoothStatusCodes.REASON_SYSTEM_POLICY, 108 BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED, 109 BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED, 110 BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX, 111 }) 112 @interface PresetSelectionFailureReason {} 113 114 /** 115 * Invoked inform about the result of a failed preset change attempt. 116 * 117 * @param device remote device, 118 * @param reason failure reason. 119 * @hide 120 */ 121 @SystemApi onPresetSelectionFailed( @onNull BluetoothDevice device, @PresetSelectionFailureReason int reason)122 void onPresetSelectionFailed( 123 @NonNull BluetoothDevice device, @PresetSelectionFailureReason int reason); 124 125 /** @hide */ 126 @Retention(RetentionPolicy.SOURCE) 127 @IntDef( 128 value = { 129 // needed for future release compatibility 130 BluetoothStatusCodes.ERROR_UNKNOWN, 131 BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST, 132 BluetoothStatusCodes.REASON_SYSTEM_POLICY, 133 BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED, 134 BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED, 135 BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX, 136 BluetoothStatusCodes.ERROR_CSIP_INVALID_GROUP_ID, 137 }) 138 @interface GroupPresetSelectionFailureReason {} 139 140 /** 141 * Invoked to inform about the result of a failed preset change attempt. 142 * 143 * <p>The implementation will try to restore the state for every device back to original 144 * 145 * @param hapGroupId valid HAP group ID, 146 * @param reason failure reason. 147 * @hide 148 */ 149 @SystemApi onPresetSelectionForGroupFailed( int hapGroupId, @GroupPresetSelectionFailureReason int reason)150 void onPresetSelectionForGroupFailed( 151 int hapGroupId, @GroupPresetSelectionFailureReason int reason); 152 153 /** @hide */ 154 @Retention(RetentionPolicy.SOURCE) 155 @IntDef( 156 value = { 157 // needed for future release compatibility 158 BluetoothStatusCodes.ERROR_UNKNOWN, 159 BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST, 160 BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST, 161 BluetoothStatusCodes.REASON_REMOTE_REQUEST, 162 BluetoothStatusCodes.REASON_SYSTEM_POLICY, 163 }) 164 @interface PresetInfoChangeReason {} 165 166 /** 167 * Invoked to inform about the preset list changes. 168 * 169 * @param device remote device, 170 * @param presetInfoList a list of all preset information on the target device 171 * @param reason reason for the preset list change 172 * @hide 173 */ 174 @SystemApi onPresetInfoChanged( @onNull BluetoothDevice device, @NonNull List<BluetoothHapPresetInfo> presetInfoList, @PresetInfoChangeReason int reason)175 void onPresetInfoChanged( 176 @NonNull BluetoothDevice device, 177 @NonNull List<BluetoothHapPresetInfo> presetInfoList, 178 @PresetInfoChangeReason int reason); 179 180 /** @hide */ 181 @Retention(RetentionPolicy.SOURCE) 182 @IntDef( 183 value = { 184 // needed for future release compatibility 185 BluetoothStatusCodes.ERROR_UNKNOWN, 186 BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST, 187 BluetoothStatusCodes.REASON_SYSTEM_POLICY, 188 BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED, 189 BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED, 190 BluetoothStatusCodes.ERROR_HAP_PRESET_NAME_TOO_LONG, 191 BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX, 192 }) 193 @interface PresetNameChangeFailureReason {} 194 195 /** 196 * Invoked to inform about the failed preset rename attempt. 197 * 198 * @param device remote device 199 * @param reason Failure reason code. 200 * @hide 201 */ 202 @SystemApi onSetPresetNameFailed( @onNull BluetoothDevice device, @PresetNameChangeFailureReason int reason)203 void onSetPresetNameFailed( 204 @NonNull BluetoothDevice device, @PresetNameChangeFailureReason int reason); 205 206 /** @hide */ 207 @Retention(RetentionPolicy.SOURCE) 208 @IntDef( 209 value = { 210 // needed for future release compatibility 211 BluetoothStatusCodes.ERROR_UNKNOWN, 212 BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST, 213 BluetoothStatusCodes.REASON_SYSTEM_POLICY, 214 BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED, 215 BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED, 216 BluetoothStatusCodes.ERROR_HAP_PRESET_NAME_TOO_LONG, 217 BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX, 218 BluetoothStatusCodes.ERROR_CSIP_INVALID_GROUP_ID, 219 }) 220 @interface GroupPresetNameChangeFailureReason {} 221 222 /** 223 * Invoked to inform about the failed preset rename attempt. 224 * 225 * <p>The implementation will try to restore the state for every device back to original 226 * 227 * @param hapGroupId valid HAP group ID, 228 * @param reason Failure reason code. 229 * @hide 230 */ 231 @SystemApi onSetPresetNameForGroupFailed( int hapGroupId, @GroupPresetNameChangeFailureReason int reason)232 void onSetPresetNameForGroupFailed( 233 int hapGroupId, @GroupPresetNameChangeFailureReason int reason); 234 } 235 236 @SuppressLint("AndroidFrameworkBluetoothPermission") 237 private final IBluetoothHapClientCallback mCallback = 238 new IBluetoothHapClientCallback.Stub() { 239 @Override 240 public void onPresetSelected( 241 @NonNull BluetoothDevice device, int presetIndex, int reasonCode) { 242 Attributable.setAttributionSource(device, mAttributionSource); 243 for (Map.Entry<BluetoothHapClient.Callback, Executor> callbackExecutorEntry : 244 mCallbackExecutorMap.entrySet()) { 245 BluetoothHapClient.Callback callback = callbackExecutorEntry.getKey(); 246 Executor executor = callbackExecutorEntry.getValue(); 247 executor.execute( 248 () -> callback.onPresetSelected(device, presetIndex, reasonCode)); 249 } 250 } 251 252 @Override 253 public void onPresetSelectionFailed(@NonNull BluetoothDevice device, int status) { 254 Attributable.setAttributionSource(device, mAttributionSource); 255 for (Map.Entry<BluetoothHapClient.Callback, Executor> callbackExecutorEntry : 256 mCallbackExecutorMap.entrySet()) { 257 BluetoothHapClient.Callback callback = callbackExecutorEntry.getKey(); 258 Executor executor = callbackExecutorEntry.getValue(); 259 executor.execute(() -> callback.onPresetSelectionFailed(device, status)); 260 } 261 } 262 263 @Override 264 public void onPresetSelectionForGroupFailed(int hapGroupId, int statusCode) { 265 for (Map.Entry<BluetoothHapClient.Callback, Executor> callbackExecutorEntry : 266 mCallbackExecutorMap.entrySet()) { 267 BluetoothHapClient.Callback callback = callbackExecutorEntry.getKey(); 268 Executor executor = callbackExecutorEntry.getValue(); 269 executor.execute( 270 () -> 271 callback.onPresetSelectionForGroupFailed( 272 hapGroupId, statusCode)); 273 } 274 } 275 276 @Override 277 public void onPresetInfoChanged( 278 @NonNull BluetoothDevice device, 279 @NonNull List<BluetoothHapPresetInfo> presetInfoList, 280 int statusCode) { 281 Attributable.setAttributionSource(device, mAttributionSource); 282 for (Map.Entry<BluetoothHapClient.Callback, Executor> callbackExecutorEntry : 283 mCallbackExecutorMap.entrySet()) { 284 BluetoothHapClient.Callback callback = callbackExecutorEntry.getKey(); 285 Executor executor = callbackExecutorEntry.getValue(); 286 executor.execute( 287 () -> 288 callback.onPresetInfoChanged( 289 device, presetInfoList, statusCode)); 290 } 291 } 292 293 @Override 294 public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int status) { 295 Attributable.setAttributionSource(device, mAttributionSource); 296 for (Map.Entry<BluetoothHapClient.Callback, Executor> callbackExecutorEntry : 297 mCallbackExecutorMap.entrySet()) { 298 BluetoothHapClient.Callback callback = callbackExecutorEntry.getKey(); 299 Executor executor = callbackExecutorEntry.getValue(); 300 executor.execute(() -> callback.onSetPresetNameFailed(device, status)); 301 } 302 } 303 304 @Override 305 public void onSetPresetNameForGroupFailed(int hapGroupId, int status) { 306 for (Map.Entry<BluetoothHapClient.Callback, Executor> callbackExecutorEntry : 307 mCallbackExecutorMap.entrySet()) { 308 BluetoothHapClient.Callback callback = callbackExecutorEntry.getKey(); 309 Executor executor = callbackExecutorEntry.getValue(); 310 executor.execute( 311 () -> callback.onSetPresetNameForGroupFailed(hapGroupId, status)); 312 } 313 } 314 }; 315 316 /** 317 * Intent used to broadcast the change in connection state of the Hearing Access Profile Client 318 * service. Please note that in the binaural case, there will be two different LE devices for 319 * the left and right side and each device will have their own connection state changes. 320 * 321 * <p>This intent will have 3 extras: 322 * 323 * <ul> 324 * <li>{@link #EXTRA_STATE} - The current state of the profile. 325 * <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. 326 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. 327 * </ul> 328 * 329 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link 330 * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link 331 * #STATE_DISCONNECTING}. 332 * 333 * @hide 334 */ 335 @SystemApi 336 @RequiresBluetoothConnectPermission 337 @RequiresPermission( 338 allOf = { 339 android.Manifest.permission.BLUETOOTH_CONNECT, 340 android.Manifest.permission.BLUETOOTH_PRIVILEGED 341 }) 342 @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) 343 public static final String ACTION_HAP_CONNECTION_STATE_CHANGED = 344 "android.bluetooth.action.HAP_CONNECTION_STATE_CHANGED"; 345 346 /** 347 * Intent used to broadcast the device availability change and the availability of its presets. 348 * Please note that in the binaural case, there will be two different LE devices for the left 349 * and right side and each device will have their own availability event. 350 * 351 * <p>This intent will have 2 extras: 352 * 353 * <ul> 354 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. 355 * <li>{@link #EXTRA_HAP_FEATURES} - Supported features map. 356 * </ul> 357 * 358 * @hide 359 */ 360 @RequiresBluetoothConnectPermission 361 @RequiresPermission( 362 allOf = { 363 android.Manifest.permission.BLUETOOTH_CONNECT, 364 android.Manifest.permission.BLUETOOTH_PRIVILEGED 365 }) 366 @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) 367 public static final String ACTION_HAP_DEVICE_AVAILABLE = 368 "android.bluetooth.action.HAP_DEVICE_AVAILABLE"; 369 370 /** 371 * Contains a list of all available presets 372 * 373 * @hide 374 */ 375 public static final String EXTRA_HAP_FEATURES = "android.bluetooth.extra.HAP_FEATURES"; 376 377 /** 378 * Represents an invalid index value. This is usually value returned in a currently active 379 * preset request for a device which is not connected. This value shouldn't be used in the API 380 * calls. 381 * 382 * @hide 383 */ 384 @SystemApi 385 @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) 386 public static final int PRESET_INDEX_UNAVAILABLE = IBluetoothHapClient.PRESET_INDEX_UNAVAILABLE; 387 388 /** 389 * Hearing aid type value. Indicates this Bluetooth device is belongs to a binaural hearing aid 390 * set. A binaural hearing aid set is two hearing aids that form a Coordinated Set, one for the 391 * right ear and one for the left ear of the user. Typically used by a user with bilateral 392 * hearing loss. 393 * 394 * @hide 395 */ 396 @SystemApi public static final int TYPE_BINAURAL = 0b00; 397 398 /** 399 * Hearing aid type value. Indicates this Bluetooth device is a single hearing aid for the left 400 * or the right ear. Typically used by a user with unilateral hearing loss. 401 * 402 * @hide 403 */ 404 @SystemApi public static final int TYPE_MONAURAL = 0b01; 405 406 /** 407 * Hearing aid type value. Indicates this Bluetooth device is two hearing aids with a connection 408 * to one another that expose a single Bluetooth radio interface. 409 * 410 * @hide 411 */ 412 @SystemApi public static final int TYPE_BANDED = 0b10; 413 414 /** 415 * Hearing aid type value. This value is reserved for future use. 416 * 417 * @hide 418 */ 419 @SystemApi public static final int TYPE_RFU = 0b11; 420 421 /** @hide */ 422 @Retention(RetentionPolicy.SOURCE) 423 @IntDef( 424 flag = true, 425 value = { 426 TYPE_BINAURAL, 427 TYPE_MONAURAL, 428 TYPE_BANDED, 429 TYPE_RFU, 430 }) 431 @interface HearingAidType {} 432 433 /** 434 * Feature mask value. 435 * 436 * @hide 437 */ 438 public static final int FEATURE_HEARING_AID_TYPE_MASK = 0b11; 439 440 /** 441 * Feature mask value. 442 * 443 * @hide 444 */ 445 public static final int FEATURE_SYNCHRONIZATED_PRESETS_MASK = 446 1 << IBluetoothHapClient.FEATURE_BIT_NUM_SYNCHRONIZATED_PRESETS; 447 448 /** 449 * Feature mask value. 450 * 451 * @hide 452 */ 453 public static final int FEATURE_INDEPENDENT_PRESETS_MASK = 454 1 << IBluetoothHapClient.FEATURE_BIT_NUM_INDEPENDENT_PRESETS; 455 456 /** 457 * Feature mask value. 458 * 459 * @hide 460 */ 461 public static final int FEATURE_DYNAMIC_PRESETS_MASK = 462 1 << IBluetoothHapClient.FEATURE_BIT_NUM_DYNAMIC_PRESETS; 463 464 /** 465 * Feature mask value. 466 * 467 * @hide 468 */ 469 public static final int FEATURE_WRITABLE_PRESETS_MASK = 470 1 << IBluetoothHapClient.FEATURE_BIT_NUM_WRITABLE_PRESETS; 471 472 /** @hide */ 473 @Retention(RetentionPolicy.SOURCE) 474 @IntDef( 475 flag = true, 476 value = { 477 FEATURE_HEARING_AID_TYPE_MASK, 478 FEATURE_SYNCHRONIZATED_PRESETS_MASK, 479 FEATURE_INDEPENDENT_PRESETS_MASK, 480 FEATURE_DYNAMIC_PRESETS_MASK, 481 FEATURE_WRITABLE_PRESETS_MASK, 482 }) 483 @interface FeatureMask {} 484 485 private final BluetoothAdapter mAdapter; 486 private final AttributionSource mAttributionSource; 487 488 private IBluetoothHapClient mService; 489 490 /** 491 * Create a BluetoothHapClient proxy object for interacting with the local Bluetooth Hearing 492 * Access Profile (HAP) client. 493 */ BluetoothHapClient(Context context, BluetoothAdapter adapter)494 /*package*/ BluetoothHapClient(Context context, BluetoothAdapter adapter) { 495 mAdapter = adapter; 496 mAttributionSource = mAdapter.getAttributionSource(); 497 mService = null; 498 499 mCloseGuard = new CloseGuard(); 500 mCloseGuard.open("close"); 501 } 502 503 /** @hide */ 504 @SuppressWarnings("Finalize") // TODO(b/314811467) finalize()505 protected void finalize() { 506 if (mCloseGuard != null) { 507 mCloseGuard.warnIfOpen(); 508 } 509 close(); 510 } 511 512 /** @hide */ 513 @Override close()514 public void close() { 515 if (VDBG) log("close()"); 516 517 mAdapter.closeProfileProxy(this); 518 } 519 520 /** @hide */ 521 @Override onServiceConnected(IBinder service)522 public void onServiceConnected(IBinder service) { 523 mService = IBluetoothHapClient.Stub.asInterface(service); 524 // re-register the service-to-app callback 525 synchronized (mCallbackExecutorMap) { 526 if (mCallbackExecutorMap.isEmpty()) { 527 return; 528 } 529 530 try { 531 if (service != null) { 532 mService.registerCallback(mCallback, mAttributionSource); 533 } 534 } catch (RemoteException e) { 535 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 536 } 537 } 538 } 539 540 /** @hide */ 541 @Override onServiceDisconnected()542 public void onServiceDisconnected() { 543 mService = null; 544 } 545 getService()546 private IBluetoothHapClient getService() { 547 return mService; 548 } 549 550 /** @hide */ 551 @Override getAdapter()552 public BluetoothAdapter getAdapter() { 553 return mAdapter; 554 } 555 556 /** 557 * Register a {@link Callback} that will be invoked during the operation of this profile. 558 * 559 * <p>Repeated registration of the same <var>callback</var> object after the first call to this 560 * method will result with IllegalArgumentException being thrown, even when the 561 * <var>executor</var> is different. API caller would have to call {@link 562 * #unregisterCallback(Callback)} with the same callback object before registering it again. 563 * 564 * @param executor an {@link Executor} to execute given callback 565 * @param callback user implementation of the {@link Callback} 566 * @throws NullPointerException if a null executor, or callback is given, or 567 * IllegalArgumentException if the same <var>callback</var> is already registered. 568 * @hide 569 */ 570 @SystemApi 571 @RequiresBluetoothConnectPermission 572 @RequiresPermission( 573 allOf = { 574 android.Manifest.permission.BLUETOOTH_CONNECT, 575 android.Manifest.permission.BLUETOOTH_PRIVILEGED 576 }) registerCallback( @onNull @allbackExecutor Executor executor, @NonNull Callback callback)577 public void registerCallback( 578 @NonNull @CallbackExecutor Executor executor, @NonNull Callback callback) { 579 Objects.requireNonNull(executor, "executor cannot be null"); 580 Objects.requireNonNull(callback, "callback cannot be null"); 581 582 if (DBG) log("registerCallback"); 583 584 synchronized (mCallbackExecutorMap) { 585 // If the callback map is empty, we register the service-to-app callback 586 if (mCallbackExecutorMap.isEmpty()) { 587 if (!isEnabled()) { 588 /* If Bluetooth is off, just store callback and it will be registered 589 * when Bluetooth is on 590 */ 591 mCallbackExecutorMap.put(callback, executor); 592 return; 593 } 594 try { 595 final IBluetoothHapClient service = getService(); 596 if (service != null) { 597 service.registerCallback(mCallback, mAttributionSource); 598 } 599 } catch (RemoteException e) { 600 throw e.rethrowAsRuntimeException(); 601 } 602 } 603 604 // Adds the passed in callback to our map of callbacks to executors 605 if (mCallbackExecutorMap.containsKey(callback)) { 606 throw new IllegalArgumentException("This callback has already been registered"); 607 } 608 mCallbackExecutorMap.put(callback, executor); 609 } 610 } 611 612 /** 613 * Unregister the specified {@link Callback}. 614 * 615 * <p>The same {@link Callback} object used when calling {@link #registerCallback(Executor, 616 * Callback)} must be used. 617 * 618 * <p>Callbacks are automatically unregistered when application process goes away 619 * 620 * @param callback user implementation of the {@link Callback} 621 * @throws NullPointerException when callback is null or IllegalArgumentException when no 622 * callback is registered 623 * @hide 624 */ 625 @SystemApi 626 @RequiresBluetoothConnectPermission 627 @RequiresPermission( 628 allOf = { 629 android.Manifest.permission.BLUETOOTH_CONNECT, 630 android.Manifest.permission.BLUETOOTH_PRIVILEGED 631 }) unregisterCallback(@onNull Callback callback)632 public void unregisterCallback(@NonNull Callback callback) { 633 Objects.requireNonNull(callback, "callback cannot be null"); 634 635 if (DBG) log("unregisterCallback"); 636 637 synchronized (mCallbackExecutorMap) { 638 if (mCallbackExecutorMap.remove(callback) == null) { 639 throw new IllegalArgumentException("This callback has not been registered"); 640 } 641 } 642 643 // If the callback map is empty, we unregister the service-to-app callback 644 if (mCallbackExecutorMap.isEmpty()) { 645 try { 646 final IBluetoothHapClient service = getService(); 647 if (service != null) { 648 service.unregisterCallback(mCallback, mAttributionSource); 649 } 650 } catch (RemoteException e) { 651 throw e.rethrowAsRuntimeException(); 652 } 653 } 654 } 655 656 /** 657 * Set connection policy of the profile 658 * 659 * <p>The device should already be paired. Connection policy can be one of {@link 660 * #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, {@link 661 * #CONNECTION_POLICY_UNKNOWN} 662 * 663 * @param device Paired bluetooth device 664 * @param connectionPolicy is the connection policy to set to for this profile 665 * @return {@code true} if connectionPolicy is set, {@code false} on error 666 * @hide 667 */ 668 @SystemApi 669 @RequiresBluetoothConnectPermission 670 @RequiresPermission( 671 allOf = { 672 android.Manifest.permission.BLUETOOTH_CONNECT, 673 android.Manifest.permission.BLUETOOTH_PRIVILEGED 674 }) setConnectionPolicy( @onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)675 public boolean setConnectionPolicy( 676 @NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { 677 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 678 Objects.requireNonNull(device, "BluetoothDevice cannot be null"); 679 final IBluetoothHapClient service = getService(); 680 if (service == null) { 681 Log.w(TAG, "Proxy not attached to service"); 682 if (DBG) log(Log.getStackTraceString(new Throwable())); 683 } else if (mAdapter.isEnabled() 684 && isValidDevice(device) 685 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 686 || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { 687 try { 688 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); 689 } catch (RemoteException e) { 690 throw e.rethrowAsRuntimeException(); 691 } 692 } 693 return false; 694 } 695 696 /** 697 * Get the connection policy of the profile. 698 * 699 * <p>The connection policy can be any of: {@link #CONNECTION_POLICY_ALLOWED}, {@link 700 * #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 701 * 702 * @param device Bluetooth device 703 * @return connection policy of the device or {@link #CONNECTION_POLICY_FORBIDDEN} if device is 704 * null 705 * @hide 706 */ 707 @SystemApi 708 @RequiresBluetoothConnectPermission 709 @RequiresPermission( 710 allOf = { 711 android.Manifest.permission.BLUETOOTH_CONNECT, 712 android.Manifest.permission.BLUETOOTH_PRIVILEGED 713 }) getConnectionPolicy(@ullable BluetoothDevice device)714 public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) { 715 if (VDBG) log("getConnectionPolicy(" + device + ")"); 716 final IBluetoothHapClient service = getService(); 717 if (service == null) { 718 Log.w(TAG, "Proxy not attached to service"); 719 if (DBG) log(Log.getStackTraceString(new Throwable())); 720 } else if (mAdapter.isEnabled() && isValidDevice(device)) { 721 try { 722 return service.getConnectionPolicy(device, mAttributionSource); 723 } catch (RemoteException e) { 724 throw e.rethrowAsRuntimeException(); 725 } 726 } 727 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 728 } 729 730 /** 731 * {@inheritDoc} 732 * 733 * @hide 734 */ 735 @SystemApi 736 @RequiresBluetoothConnectPermission 737 @RequiresPermission( 738 allOf = { 739 android.Manifest.permission.BLUETOOTH_CONNECT, 740 android.Manifest.permission.BLUETOOTH_PRIVILEGED 741 }) 742 @Override getConnectedDevices()743 public @NonNull List<BluetoothDevice> getConnectedDevices() { 744 if (VDBG) Log.d(TAG, "getConnectedDevices()"); 745 final IBluetoothHapClient service = getService(); 746 if (service == null) { 747 Log.w(TAG, "Proxy not attached to service"); 748 if (DBG) log(Log.getStackTraceString(new Throwable())); 749 } else if (isEnabled()) { 750 try { 751 return Attributable.setAttributionSource( 752 service.getConnectedDevices(mAttributionSource), mAttributionSource); 753 } catch (RemoteException e) { 754 throw e.rethrowAsRuntimeException(); 755 } 756 } 757 return Collections.emptyList(); 758 } 759 760 /** 761 * {@inheritDoc} 762 * 763 * @hide 764 */ 765 @SystemApi 766 @RequiresBluetoothConnectPermission 767 @RequiresPermission( 768 allOf = { 769 android.Manifest.permission.BLUETOOTH_CONNECT, 770 android.Manifest.permission.BLUETOOTH_PRIVILEGED 771 }) 772 @Override 773 @NonNull getDevicesMatchingConnectionStates(@onNull int[] states)774 public List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) { 775 if (VDBG) Log.d(TAG, "getDevicesMatchingConnectionStates()"); 776 final IBluetoothHapClient service = getService(); 777 if (service == null) { 778 Log.w(TAG, "Proxy not attached to service"); 779 if (DBG) log(Log.getStackTraceString(new Throwable())); 780 } else if (isEnabled()) { 781 try { 782 return Attributable.setAttributionSource( 783 service.getDevicesMatchingConnectionStates(states, mAttributionSource), 784 mAttributionSource); 785 } catch (RemoteException e) { 786 throw e.rethrowAsRuntimeException(); 787 } 788 } 789 return Collections.emptyList(); 790 } 791 792 /** 793 * {@inheritDoc} 794 * 795 * @hide 796 */ 797 @SystemApi 798 @RequiresBluetoothConnectPermission 799 @RequiresPermission( 800 allOf = { 801 android.Manifest.permission.BLUETOOTH_CONNECT, 802 android.Manifest.permission.BLUETOOTH_PRIVILEGED 803 }) 804 @Override 805 @BluetoothProfile.BtProfileState getConnectionState(@onNull BluetoothDevice device)806 public int getConnectionState(@NonNull BluetoothDevice device) { 807 if (VDBG) Log.d(TAG, "getConnectionState(" + device + ")"); 808 final IBluetoothHapClient service = getService(); 809 if (service == null) { 810 Log.w(TAG, "Proxy not attached to service"); 811 if (DBG) log(Log.getStackTraceString(new Throwable())); 812 } else if (isEnabled() && isValidDevice(device)) { 813 try { 814 return service.getConnectionState(device, mAttributionSource); 815 } catch (RemoteException e) { 816 throw e.rethrowAsRuntimeException(); 817 } 818 } 819 return BluetoothProfile.STATE_DISCONNECTED; 820 } 821 822 /** 823 * Gets the group identifier, which can be used in the group related part of the API. 824 * 825 * <p>Users are expected to get group identifier for each of the connected device to discover 826 * the device grouping. This allows them to make an informed decision which devices can be 827 * controlled by single group API call and which require individual device calls. 828 * 829 * <p>Note that some binaural HA devices may not support group operations, therefore are not 830 * considered a valid HAP group. In such case -1 is returned even if such device is a valid Le 831 * Audio Coordinated Set member. 832 * 833 * @return valid group identifier or -1 834 * @hide 835 */ 836 @SystemApi 837 @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) 838 @RequiresBluetoothConnectPermission 839 @RequiresPermission( 840 allOf = { 841 android.Manifest.permission.BLUETOOTH_CONNECT, 842 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 843 }) getHapGroup(@onNull BluetoothDevice device)844 public int getHapGroup(@NonNull BluetoothDevice device) { 845 final IBluetoothHapClient service = getService(); 846 if (service == null) { 847 Log.w(TAG, "Proxy not attached to service"); 848 if (DBG) log(Log.getStackTraceString(new Throwable())); 849 } else if (isEnabled() && isValidDevice(device)) { 850 try { 851 return service.getHapGroup(device, mAttributionSource); 852 } catch (RemoteException e) { 853 throw e.rethrowAsRuntimeException(); 854 } 855 } 856 return BluetoothCsipSetCoordinator.GROUP_ID_INVALID; 857 } 858 859 /** 860 * Gets the currently active preset for a HA device. 861 * 862 * @param device is the device for which we want to set the active preset 863 * @return active preset index 864 * @hide 865 */ 866 @SystemApi 867 @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) 868 @RequiresBluetoothConnectPermission 869 @RequiresPermission( 870 allOf = { 871 android.Manifest.permission.BLUETOOTH_CONNECT, 872 android.Manifest.permission.BLUETOOTH_PRIVILEGED 873 }) getActivePresetIndex(@onNull BluetoothDevice device)874 public int getActivePresetIndex(@NonNull BluetoothDevice device) { 875 final IBluetoothHapClient service = getService(); 876 if (service == null) { 877 Log.w(TAG, "Proxy not attached to service"); 878 if (DBG) log(Log.getStackTraceString(new Throwable())); 879 } else if (isEnabled() && isValidDevice(device)) { 880 try { 881 return service.getActivePresetIndex(device, mAttributionSource); 882 } catch (RemoteException e) { 883 throw e.rethrowAsRuntimeException(); 884 } 885 } 886 return PRESET_INDEX_UNAVAILABLE; 887 } 888 889 /** 890 * Get the currently active preset info for a remote device. 891 * 892 * @param device is the device for which we want to get the preset name 893 * @return currently active preset info if selected, null if preset info is not available for 894 * the remote device 895 * @hide 896 */ 897 @SystemApi 898 @RequiresBluetoothConnectPermission 899 @RequiresPermission( 900 allOf = { 901 android.Manifest.permission.BLUETOOTH_CONNECT, 902 android.Manifest.permission.BLUETOOTH_PRIVILEGED 903 }) getActivePresetInfo(@onNull BluetoothDevice device)904 public @Nullable BluetoothHapPresetInfo getActivePresetInfo(@NonNull BluetoothDevice device) { 905 final IBluetoothHapClient service = getService(); 906 if (service == null) { 907 Log.w(TAG, "Proxy not attached to service"); 908 if (DBG) log(Log.getStackTraceString(new Throwable())); 909 } else if (isEnabled() && isValidDevice(device)) { 910 try { 911 return service.getActivePresetInfo(device, mAttributionSource); 912 } catch (RemoteException e) { 913 throw e.rethrowAsRuntimeException(); 914 } 915 } 916 917 return null; 918 } 919 920 /** 921 * Selects the currently active preset for a HA device 922 * 923 * <p>On success, {@link Callback#onPresetSelected(BluetoothDevice, int, int)} will be called 924 * with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure, {@link 925 * Callback#onPresetSelectionFailed(BluetoothDevice, int)} will be called. 926 * 927 * @param device is the device for which we want to set the active preset 928 * @param presetIndex is an index of one of the available presets 929 * @hide 930 */ 931 @SystemApi 932 @RequiresBluetoothConnectPermission 933 @RequiresPermission( 934 allOf = { 935 android.Manifest.permission.BLUETOOTH_CONNECT, 936 android.Manifest.permission.BLUETOOTH_PRIVILEGED 937 }) selectPreset(@onNull BluetoothDevice device, int presetIndex)938 public void selectPreset(@NonNull BluetoothDevice device, int presetIndex) { 939 final IBluetoothHapClient service = getService(); 940 if (service == null) { 941 Log.w(TAG, "Proxy not attached to service"); 942 if (DBG) log(Log.getStackTraceString(new Throwable())); 943 } else if (isEnabled() && isValidDevice(device)) { 944 try { 945 service.selectPreset(device, presetIndex, mAttributionSource); 946 } catch (RemoteException e) { 947 throw e.rethrowAsRuntimeException(); 948 } 949 } 950 } 951 952 /** 953 * Selects the currently active preset for a Hearing Aid device group. 954 * 955 * <p>This group call may replace multiple device calls if those are part of the valid HAS 956 * group. Note that binaural HA devices may or may not support group. 957 * 958 * <p>On success, {@link Callback#onPresetSelected(BluetoothDevice, int, int)} will be called 959 * for each device within the group with reason code {@link 960 * BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure, {@link 961 * Callback#onPresetSelectionForGroupFailed(int, int)} will be called for the group. 962 * 963 * @param groupId is the device group identifier for which want to set the active preset 964 * @param presetIndex is an index of one of the available presets 965 * @hide 966 */ 967 @SystemApi 968 @RequiresBluetoothConnectPermission 969 @RequiresPermission( 970 allOf = { 971 android.Manifest.permission.BLUETOOTH_CONNECT, 972 android.Manifest.permission.BLUETOOTH_PRIVILEGED 973 }) selectPresetForGroup(int groupId, int presetIndex)974 public void selectPresetForGroup(int groupId, int presetIndex) { 975 final IBluetoothHapClient service = getService(); 976 if (service == null) { 977 Log.w(TAG, "Proxy not attached to service"); 978 if (DBG) log(Log.getStackTraceString(new Throwable())); 979 } else if (isEnabled()) { 980 try { 981 service.selectPresetForGroup(groupId, presetIndex, mAttributionSource); 982 } catch (RemoteException e) { 983 throw e.rethrowAsRuntimeException(); 984 } 985 } 986 } 987 988 /** 989 * Sets the next preset as a currently active preset for a HA device 990 * 991 * <p>Note that the meaning of 'next' is HA device implementation specific and does not 992 * necessarily mean a higher preset index. 993 * 994 * @param device is the device for which we want to set the active preset 995 * @hide 996 */ 997 @SystemApi 998 @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) 999 @RequiresBluetoothConnectPermission 1000 @RequiresPermission( 1001 allOf = { 1002 android.Manifest.permission.BLUETOOTH_CONNECT, 1003 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1004 }) switchToNextPreset(@onNull BluetoothDevice device)1005 public void switchToNextPreset(@NonNull BluetoothDevice device) { 1006 final IBluetoothHapClient service = getService(); 1007 if (service == null) { 1008 Log.w(TAG, "Proxy not attached to service"); 1009 if (DBG) log(Log.getStackTraceString(new Throwable())); 1010 } else if (isEnabled() && isValidDevice(device)) { 1011 try { 1012 service.switchToNextPreset(device, mAttributionSource); 1013 } catch (RemoteException e) { 1014 throw e.rethrowAsRuntimeException(); 1015 } 1016 } 1017 } 1018 1019 /** 1020 * Sets the next preset as a currently active preset for a HA device group 1021 * 1022 * <p>Note that the meaning of 'next' is HA device implementation specific and does not 1023 * necessarily mean a higher preset index. 1024 * 1025 * <p>This group call may replace multiple device calls if those are part of the valid HAS 1026 * group. Note that binaural HA devices may or may not support group. 1027 * 1028 * @param groupId is the device group identifier for which want to set the active preset 1029 * @hide 1030 */ 1031 @SystemApi 1032 @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) 1033 @RequiresBluetoothConnectPermission 1034 @RequiresPermission( 1035 allOf = { 1036 android.Manifest.permission.BLUETOOTH_CONNECT, 1037 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1038 }) switchToNextPresetForGroup(int groupId)1039 public void switchToNextPresetForGroup(int groupId) { 1040 final IBluetoothHapClient service = getService(); 1041 if (service == null) { 1042 Log.w(TAG, "Proxy not attached to service"); 1043 if (DBG) log(Log.getStackTraceString(new Throwable())); 1044 } else if (isEnabled()) { 1045 try { 1046 service.switchToNextPresetForGroup(groupId, mAttributionSource); 1047 } catch (RemoteException e) { 1048 throw e.rethrowAsRuntimeException(); 1049 } 1050 } 1051 } 1052 1053 /** 1054 * Sets the previous preset as a currently active preset for a HA device. 1055 * 1056 * <p>Note that the meaning of 'previous' is HA device implementation specific and does not 1057 * necessarily mean a lower preset index. 1058 * 1059 * @param device is the device for which we want to set the active preset 1060 * @hide 1061 */ 1062 @SystemApi 1063 @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) 1064 @RequiresBluetoothConnectPermission 1065 @RequiresPermission( 1066 allOf = { 1067 android.Manifest.permission.BLUETOOTH_CONNECT, 1068 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1069 }) switchToPreviousPreset(@onNull BluetoothDevice device)1070 public void switchToPreviousPreset(@NonNull BluetoothDevice device) { 1071 final IBluetoothHapClient service = getService(); 1072 if (service == null) { 1073 Log.w(TAG, "Proxy not attached to service"); 1074 if (DBG) log(Log.getStackTraceString(new Throwable())); 1075 } else if (isEnabled() && isValidDevice(device)) { 1076 try { 1077 service.switchToPreviousPreset(device, mAttributionSource); 1078 } catch (RemoteException e) { 1079 throw e.rethrowAsRuntimeException(); 1080 } 1081 } 1082 } 1083 1084 /** 1085 * Sets the previous preset as a currently active preset for a HA device group 1086 * 1087 * <p>Note the meaning of 'previous' is HA device implementation specific and does not 1088 * necessarily mean a lower preset index. 1089 * 1090 * <p>This group call may replace multiple device calls if those are part of the valid HAS 1091 * group. Note that binaural HA devices may or may not support group. 1092 * 1093 * @param groupId is the device group identifier for which want to set the active preset 1094 * @hide 1095 */ 1096 @SystemApi 1097 @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) 1098 @RequiresBluetoothConnectPermission 1099 @RequiresPermission( 1100 allOf = { 1101 android.Manifest.permission.BLUETOOTH_CONNECT, 1102 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1103 }) switchToPreviousPresetForGroup(int groupId)1104 public void switchToPreviousPresetForGroup(int groupId) { 1105 final IBluetoothHapClient service = getService(); 1106 if (service == null) { 1107 Log.w(TAG, "Proxy not attached to service"); 1108 if (DBG) log(Log.getStackTraceString(new Throwable())); 1109 } else if (isEnabled()) { 1110 try { 1111 service.switchToPreviousPresetForGroup(groupId, mAttributionSource); 1112 } catch (RemoteException e) { 1113 throw e.rethrowAsRuntimeException(); 1114 } 1115 } 1116 } 1117 1118 /** 1119 * Requests the preset info 1120 * 1121 * @param device is the device for which we want to get the preset name 1122 * @param presetIndex is an index of one of the available presets 1123 * @return preset info 1124 * @hide 1125 */ 1126 @SystemApi 1127 @FlaggedApi(Flags.FLAG_SETTINGS_CAN_CONTROL_HAP_PRESET) 1128 @RequiresBluetoothConnectPermission 1129 @RequiresPermission( 1130 allOf = { 1131 android.Manifest.permission.BLUETOOTH_CONNECT, 1132 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1133 }) 1134 @Nullable getPresetInfo(@onNull BluetoothDevice device, int presetIndex)1135 public BluetoothHapPresetInfo getPresetInfo(@NonNull BluetoothDevice device, int presetIndex) { 1136 final IBluetoothHapClient service = getService(); 1137 if (service == null) { 1138 Log.w(TAG, "Proxy not attached to service"); 1139 if (DBG) log(Log.getStackTraceString(new Throwable())); 1140 } else if (isEnabled() && isValidDevice(device)) { 1141 try { 1142 return service.getPresetInfo(device, presetIndex, mAttributionSource); 1143 } catch (RemoteException e) { 1144 throw e.rethrowAsRuntimeException(); 1145 } 1146 } 1147 return null; 1148 } 1149 1150 /** 1151 * Get all preset info for a particular device 1152 * 1153 * @param device is the device for which we want to get all presets info 1154 * @return a list of all known preset info 1155 * @hide 1156 */ 1157 @SystemApi 1158 @RequiresBluetoothConnectPermission 1159 @RequiresPermission( 1160 allOf = { 1161 android.Manifest.permission.BLUETOOTH_CONNECT, 1162 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1163 }) getAllPresetInfo(@onNull BluetoothDevice device)1164 public @NonNull List<BluetoothHapPresetInfo> getAllPresetInfo(@NonNull BluetoothDevice device) { 1165 final IBluetoothHapClient service = getService(); 1166 if (service == null) { 1167 Log.w(TAG, "Proxy not attached to service"); 1168 if (DBG) log(Log.getStackTraceString(new Throwable())); 1169 } else if (isEnabled() && isValidDevice(device)) { 1170 try { 1171 return service.getAllPresetInfo(device, mAttributionSource); 1172 } catch (RemoteException e) { 1173 throw e.rethrowAsRuntimeException(); 1174 } 1175 } 1176 return Collections.emptyList(); 1177 } 1178 1179 /** 1180 * Requests HAP features 1181 * 1182 * @param device is the device for which we want to get features for 1183 * @return features value with feature bits set 1184 * @hide 1185 */ 1186 @RequiresBluetoothConnectPermission 1187 @RequiresPermission( 1188 allOf = { 1189 android.Manifest.permission.BLUETOOTH_CONNECT, 1190 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1191 }) getFeatures(@onNull BluetoothDevice device)1192 public int getFeatures(@NonNull BluetoothDevice device) { 1193 final IBluetoothHapClient service = getService(); 1194 if (service == null) { 1195 Log.w(TAG, "Proxy not attached to service"); 1196 if (DBG) log(Log.getStackTraceString(new Throwable())); 1197 } else if (isEnabled() && isValidDevice(device)) { 1198 try { 1199 return service.getFeatures(device, mAttributionSource); 1200 } catch (RemoteException e) { 1201 throw e.rethrowAsRuntimeException(); 1202 } 1203 } 1204 return 0x00; 1205 } 1206 1207 /** 1208 * Retrieves hearing aid type from feature value. 1209 * 1210 * @param device is the device for which we want to get the hearing aid type 1211 * @return hearing aid type 1212 * @hide 1213 */ 1214 @SystemApi 1215 @RequiresBluetoothConnectPermission 1216 @RequiresPermission( 1217 allOf = { 1218 android.Manifest.permission.BLUETOOTH_CONNECT, 1219 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1220 }) 1221 @HearingAidType getHearingAidType(@onNull BluetoothDevice device)1222 public int getHearingAidType(@NonNull BluetoothDevice device) { 1223 return getFeatures(device) & FEATURE_HEARING_AID_TYPE_MASK; 1224 } 1225 1226 /** 1227 * Retrieves if this device supports synchronized presets or not from feature value. 1228 * 1229 * @param device is the device for which we want to know if it supports synchronized presets 1230 * @return {@code true} if the device supports synchronized presets, {@code false} otherwise 1231 * @hide 1232 */ 1233 @SystemApi 1234 @RequiresBluetoothConnectPermission 1235 @RequiresPermission( 1236 allOf = { 1237 android.Manifest.permission.BLUETOOTH_CONNECT, 1238 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1239 }) supportsSynchronizedPresets(@onNull BluetoothDevice device)1240 public boolean supportsSynchronizedPresets(@NonNull BluetoothDevice device) { 1241 return (getFeatures(device) & FEATURE_SYNCHRONIZATED_PRESETS_MASK) 1242 == FEATURE_SYNCHRONIZATED_PRESETS_MASK; 1243 } 1244 1245 /** 1246 * Retrieves if this device supports independent presets or not from feature value. 1247 * 1248 * @param device is the device for which we want to know if it supports independent presets 1249 * @return {@code true} if the device supports independent presets, {@code false} otherwise 1250 * @hide 1251 */ 1252 @SystemApi 1253 @RequiresBluetoothConnectPermission 1254 @RequiresPermission( 1255 allOf = { 1256 android.Manifest.permission.BLUETOOTH_CONNECT, 1257 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1258 }) supportsIndependentPresets(@onNull BluetoothDevice device)1259 public boolean supportsIndependentPresets(@NonNull BluetoothDevice device) { 1260 return (getFeatures(device) & FEATURE_INDEPENDENT_PRESETS_MASK) 1261 == FEATURE_INDEPENDENT_PRESETS_MASK; 1262 } 1263 1264 /** 1265 * Retrieves if this device supports dynamic presets or not from feature value. 1266 * 1267 * @param device is the device for which we want to know if it supports dynamic presets 1268 * @return {@code true} if the device supports dynamic presets, {@code false} otherwise 1269 * @hide 1270 */ 1271 @SystemApi 1272 @RequiresBluetoothConnectPermission 1273 @RequiresPermission( 1274 allOf = { 1275 android.Manifest.permission.BLUETOOTH_CONNECT, 1276 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1277 }) supportsDynamicPresets(@onNull BluetoothDevice device)1278 public boolean supportsDynamicPresets(@NonNull BluetoothDevice device) { 1279 return (getFeatures(device) & FEATURE_DYNAMIC_PRESETS_MASK) == FEATURE_DYNAMIC_PRESETS_MASK; 1280 } 1281 1282 /** 1283 * Retrieves if this device supports writable presets or not from feature value. 1284 * 1285 * @param device is the device for which we want to know if it supports writable presets 1286 * @return {@code true} if the device supports writable presets, {@code false} otherwise 1287 * @hide 1288 */ 1289 @SystemApi 1290 @RequiresBluetoothConnectPermission 1291 @RequiresPermission( 1292 allOf = { 1293 android.Manifest.permission.BLUETOOTH_CONNECT, 1294 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1295 }) supportsWritablePresets(@onNull BluetoothDevice device)1296 public boolean supportsWritablePresets(@NonNull BluetoothDevice device) { 1297 return (getFeatures(device) & FEATURE_WRITABLE_PRESETS_MASK) 1298 == FEATURE_WRITABLE_PRESETS_MASK; 1299 } 1300 1301 /** 1302 * Sets the preset name for a particular device 1303 * 1304 * <p>Note that the name length is restricted to 40 characters. 1305 * 1306 * <p>On success, {@link Callback#onPresetInfoChanged(BluetoothDevice, List, int)} with a new 1307 * name will be called and reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On 1308 * failure, {@link Callback#onSetPresetNameFailed(BluetoothDevice, int)} will be called. 1309 * 1310 * @param device is the device for which we want to get the preset name 1311 * @param presetIndex is an index of one of the available presets 1312 * @param name is a new name for a preset, maximum length is 40 characters 1313 * @hide 1314 */ 1315 @SystemApi 1316 @RequiresBluetoothConnectPermission 1317 @RequiresPermission( 1318 allOf = { 1319 android.Manifest.permission.BLUETOOTH_CONNECT, 1320 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1321 }) setPresetName( @onNull BluetoothDevice device, int presetIndex, @NonNull String name)1322 public void setPresetName( 1323 @NonNull BluetoothDevice device, int presetIndex, @NonNull String name) { 1324 final IBluetoothHapClient service = getService(); 1325 if (service == null) { 1326 Log.w(TAG, "Proxy not attached to service"); 1327 if (DBG) log(Log.getStackTraceString(new Throwable())); 1328 } else if (isEnabled() && isValidDevice(device)) { 1329 try { 1330 service.setPresetName(device, presetIndex, name, mAttributionSource); 1331 } catch (RemoteException e) { 1332 throw e.rethrowAsRuntimeException(); 1333 } 1334 } 1335 } 1336 1337 /** 1338 * Sets the name for a hearing aid preset. 1339 * 1340 * <p>Note that the name length is restricted to 40 characters. 1341 * 1342 * <p>On success, {@link Callback#onPresetInfoChanged(BluetoothDevice, List, int)} with a new 1343 * name will be called for each device within the group with reason code {@link 1344 * BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST} On failure, {@link 1345 * Callback#onSetPresetNameForGroupFailed(int, int)} will be invoked 1346 * 1347 * @param groupId is the device group identifier 1348 * @param presetIndex is an index of one of the available presets 1349 * @param name is a new name for a preset, maximum length is 40 characters 1350 * @hide 1351 */ 1352 @SystemApi 1353 @RequiresBluetoothConnectPermission 1354 @RequiresPermission( 1355 allOf = { 1356 android.Manifest.permission.BLUETOOTH_CONNECT, 1357 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1358 }) setPresetNameForGroup(int groupId, int presetIndex, @NonNull String name)1359 public void setPresetNameForGroup(int groupId, int presetIndex, @NonNull String name) { 1360 final IBluetoothHapClient service = getService(); 1361 if (service == null) { 1362 Log.w(TAG, "Proxy not attached to service"); 1363 if (DBG) log(Log.getStackTraceString(new Throwable())); 1364 } else if (isEnabled()) { 1365 try { 1366 service.setPresetNameForGroup(groupId, presetIndex, name, mAttributionSource); 1367 } catch (RemoteException e) { 1368 throw e.rethrowAsRuntimeException(); 1369 } 1370 } 1371 } 1372 isEnabled()1373 private boolean isEnabled() { 1374 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 1375 return false; 1376 } 1377 isValidDevice(BluetoothDevice device)1378 private boolean isValidDevice(BluetoothDevice device) { 1379 if (device == null) return false; 1380 1381 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 1382 return false; 1383 } 1384 log(String msg)1385 private static void log(String msg) { 1386 Log.d(TAG, msg); 1387 } 1388 } 1389