1 /* 2 * Copyright 2020 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 static android.bluetooth.BluetoothUtils.getSyncTimeout; 21 22 import android.annotation.CallbackExecutor; 23 import android.annotation.IntDef; 24 import android.annotation.IntRange; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.RequiresPermission; 28 import android.annotation.SdkConstant; 29 import android.annotation.SdkConstant.SdkConstantType; 30 import android.annotation.SuppressLint; 31 import android.annotation.SystemApi; 32 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 33 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; 34 import android.content.AttributionSource; 35 import android.content.Context; 36 import android.os.IBinder; 37 import android.os.RemoteException; 38 import android.util.CloseGuard; 39 import android.util.Log; 40 41 import com.android.modules.utils.SynchronousResultReceiver; 42 43 import java.lang.annotation.Retention; 44 import java.lang.annotation.RetentionPolicy; 45 import java.util.ArrayList; 46 import java.util.HashMap; 47 import java.util.List; 48 import java.util.Map; 49 import java.util.Objects; 50 import java.util.concurrent.Executor; 51 import java.util.concurrent.TimeoutException; 52 53 /** 54 * This class provides the public APIs to control the LeAudio profile. 55 * 56 * <p>BluetoothLeAudio is a proxy object for controlling the Bluetooth LE Audio 57 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get 58 * the BluetoothLeAudio proxy object. 59 * 60 * <p> Android only supports one set of connected Bluetooth LeAudio device at a time. Each 61 * method is protected with its appropriate permission. 62 */ 63 public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { 64 private static final String TAG = "BluetoothLeAudio"; 65 private static final boolean DBG = false; 66 private static final boolean VDBG = false; 67 68 private final Map<Callback, Executor> mCallbackExecutorMap = new HashMap<>(); 69 70 private CloseGuard mCloseGuard; 71 72 /** 73 * This class provides a callback that is invoked when audio codec config changes on 74 * the remote device. 75 * 76 * @hide 77 */ 78 @SystemApi 79 public interface Callback { 80 /** @hide */ 81 @Retention(RetentionPolicy.SOURCE) 82 @IntDef(value = { 83 GROUP_STATUS_ACTIVE, 84 GROUP_STATUS_INACTIVE, 85 }) 86 @interface GroupStatus {} 87 88 /** 89 * Callback invoked when callback is registered and when codec config 90 * changes on the remote device. 91 * 92 * @param groupId the group id 93 * @param status latest codec status for this group 94 * @hide 95 */ 96 @SystemApi onCodecConfigChanged(int groupId, @NonNull BluetoothLeAudioCodecStatus status)97 void onCodecConfigChanged(int groupId, 98 @NonNull BluetoothLeAudioCodecStatus status); 99 100 /** 101 * Callback invoked when a device has been added to the group. 102 * It usually happens after connection or on bluetooth startup if 103 * the device is bonded. 104 * 105 * @param device the device which is added to the group 106 * @param groupId the group id 107 * @hide 108 */ 109 @SystemApi onGroupNodeAdded(@onNull BluetoothDevice device, int groupId)110 void onGroupNodeAdded(@NonNull BluetoothDevice device, int groupId); 111 112 /** 113 * Callback invoked when a device has been removed from the group. 114 * It usually happens when device gets unbonded. 115 * 116 * @param device the device which is removed from the group 117 * @param groupId the group id 118 * 119 * @hide 120 */ 121 @SystemApi onGroupNodeRemoved(@onNull BluetoothDevice device, int groupId)122 void onGroupNodeRemoved(@NonNull BluetoothDevice device, int groupId); 123 124 /** 125 * Callback invoked the group's active state changes. 126 * 127 * @param groupId the group id 128 * @param groupStatus active or inactive state. 129 * @hide 130 */ 131 @SystemApi onGroupStatusChanged(int groupId, @GroupStatus int groupStatus)132 void onGroupStatusChanged(int groupId, @GroupStatus int groupStatus); 133 } 134 135 @SuppressLint("AndroidFrameworkBluetoothPermission") 136 private final IBluetoothLeAudioCallback mCallback = new IBluetoothLeAudioCallback.Stub() { 137 @Override 138 public void onCodecConfigChanged(int groupId, 139 @NonNull BluetoothLeAudioCodecStatus status) { 140 for (Map.Entry<BluetoothLeAudio.Callback, Executor> callbackExecutorEntry: 141 mCallbackExecutorMap.entrySet()) { 142 BluetoothLeAudio.Callback callback = callbackExecutorEntry.getKey(); 143 Executor executor = callbackExecutorEntry.getValue(); 144 executor.execute(() -> callback.onCodecConfigChanged(groupId, status)); 145 } 146 } 147 148 @Override 149 public void onGroupNodeAdded(@NonNull BluetoothDevice device, int groupId) { 150 Attributable.setAttributionSource(device, mAttributionSource); 151 for (Map.Entry<BluetoothLeAudio.Callback, Executor> callbackExecutorEntry: 152 mCallbackExecutorMap.entrySet()) { 153 BluetoothLeAudio.Callback callback = callbackExecutorEntry.getKey(); 154 Executor executor = callbackExecutorEntry.getValue(); 155 executor.execute(() -> callback.onGroupNodeAdded(device, groupId)); 156 } 157 } 158 159 @Override 160 public void onGroupNodeRemoved(@NonNull BluetoothDevice device, int groupId) { 161 Attributable.setAttributionSource(device, mAttributionSource); 162 for (Map.Entry<BluetoothLeAudio.Callback, Executor> callbackExecutorEntry: 163 mCallbackExecutorMap.entrySet()) { 164 BluetoothLeAudio.Callback callback = callbackExecutorEntry.getKey(); 165 Executor executor = callbackExecutorEntry.getValue(); 166 executor.execute(() -> callback.onGroupNodeRemoved(device, groupId)); 167 } 168 } 169 170 @Override 171 public void onGroupStatusChanged(int groupId, int groupStatus) { 172 for (Map.Entry<BluetoothLeAudio.Callback, Executor> callbackExecutorEntry: 173 mCallbackExecutorMap.entrySet()) { 174 BluetoothLeAudio.Callback callback = callbackExecutorEntry.getKey(); 175 Executor executor = callbackExecutorEntry.getValue(); 176 executor.execute(() -> callback.onGroupStatusChanged(groupId, groupStatus)); 177 } 178 } 179 }; 180 181 /** 182 * Intent used to broadcast the change in connection state of the LeAudio 183 * profile. Please note that in the binaural case, there will be two different LE devices for 184 * the left and right side and each device will have their own connection state changes. 185 * 186 * <p>This intent will have 3 extras: 187 * <ul> 188 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 189 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> 190 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 191 * </ul> 192 * 193 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 194 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, 195 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. 196 */ 197 @RequiresLegacyBluetoothPermission 198 @RequiresBluetoothConnectPermission 199 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 200 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 201 public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = 202 "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED"; 203 204 /** 205 * Intent used to broadcast the selection of a connected device as active. 206 * 207 * <p>This intent will have one extra: 208 * <ul> 209 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can 210 * be null if no device is active. </li> 211 * </ul> 212 * 213 * @hide 214 */ 215 @SystemApi 216 @RequiresLegacyBluetoothPermission 217 @RequiresBluetoothConnectPermission 218 @RequiresPermission(allOf = { 219 android.Manifest.permission.BLUETOOTH_CONNECT, 220 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 221 }) 222 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 223 public static final String ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED = 224 "android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED"; 225 226 /** 227 * Indicates unspecified audio content. 228 * @hide 229 */ 230 public static final int CONTEXT_TYPE_UNSPECIFIED = 0x0001; 231 232 /** 233 * Indicates conversation between humans as, for example, in telephony or video calls. 234 * @hide 235 */ 236 public static final int CONTEXT_TYPE_CONVERSATIONAL = 0x0002; 237 238 /** 239 * Indicates media as, for example, in music, public radio, podcast or video soundtrack. 240 * @hide 241 */ 242 public static final int CONTEXT_TYPE_MEDIA = 0x0004; 243 244 /** 245 * Indicates audio associated with a video gaming. 246 * @hide 247 */ 248 public static final int CONTEXT_TYPE_GAME = 0x0008; 249 250 /** 251 * Indicates instructional audio as, for example, in navigation, announcements or user 252 * guidance. 253 * @hide 254 */ 255 public static final int CONTEXT_TYPE_INSTRUCTIONAL = 0x0010; 256 257 /** 258 * Indicates man machine communication as, for example, with voice recognition or virtual 259 * assistant. 260 * @hide 261 */ 262 public static final int CONTEXT_TYPE_VOICE_ASSISTANTS = 0x0020; 263 264 /** 265 * Indicates audio associated with a live audio stream. 266 * 267 * @hide 268 */ 269 public static final int CONTEXT_TYPE_LIVE = 0x0040; 270 271 /** 272 * Indicates sound effects as, for example, in keyboard, touch feedback; menu and user 273 * interface sounds, and other system sounds. 274 * @hide 275 */ 276 public static final int CONTEXT_TYPE_SOUND_EFFECTS = 0x0080; 277 278 /** 279 * Indicates notification and reminder sounds, attention-seeking audio, for example, in beeps 280 * signaling the arrival of a message. 281 * @hide 282 */ 283 public static final int CONTEXT_TYPE_NOTIFICATIONS = 0x0100; 284 285 286 /** 287 * Indicates ringtone as in a call alert. 288 * @hide 289 */ 290 public static final int CONTEXT_TYPE_RINGTONE = 0x0200; 291 292 /** 293 * Indicates alerts and timers, immediate alerts as, for example, in a low battery alarm, 294 * timer expiry or alarm clock. 295 * @hide 296 */ 297 public static final int CONTEXT_TYPE_ALERTS = 0x0400; 298 299 300 /** 301 * Indicates emergency alarm as, for example, with fire alarms or other urgent alerts. 302 * @hide 303 */ 304 public static final int CONTEXT_TYPE_EMERGENCY_ALARM = 0x0800; 305 306 /** 307 * This represents an invalid group ID. 308 */ 309 public static final int GROUP_ID_INVALID = IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; 310 311 /** 312 * This represents an invalid audio location. 313 * 314 * @hide 315 */ 316 @SystemApi 317 public static final int AUDIO_LOCATION_INVALID = 0; 318 319 /** 320 * This represents an audio location front left. 321 * 322 * @hide 323 */ 324 @SystemApi 325 public static final int AUDIO_LOCATION_FRONT_LEFT = 0x01 << 0; 326 327 /** 328 * This represents an audio location front right. 329 * 330 * @hide 331 */ 332 @SystemApi 333 public static final int AUDIO_LOCATION_FRONT_RIGHT = 0x01 << 1; 334 335 /** 336 * This represents an audio location front center. 337 * 338 * @hide 339 */ 340 @SystemApi 341 public static final int AUDIO_LOCATION_FRONT_CENTER = 0x01 << 2; 342 343 /** 344 * This represents an audio location low frequency effects 1. 345 * 346 * @hide 347 */ 348 @SystemApi 349 public static final int AUDIO_LOCATION_LOW_FREQ_EFFECTS_ONE = 0x01 << 3; 350 351 /** 352 * This represents an audio location back left. 353 * 354 * @hide 355 */ 356 @SystemApi 357 public static final int AUDIO_LOCATION_BACK_LEFT = 0x01 << 4; 358 359 /** 360 * This represents an audio location back right. 361 * 362 * @hide 363 */ 364 @SystemApi 365 public static final int AUDIO_LOCATION_BACK_RIGHT = 0x01 << 5; 366 367 /** 368 * This represents an audio location front left of center. 369 * 370 * @hide 371 */ 372 @SystemApi 373 public static final int AUDIO_LOCATION_FRONT_LEFT_OF_CENTER = 0x01 << 6; 374 375 /** 376 * This represents an audio location front right of center. 377 * 378 * @hide 379 */ 380 @SystemApi 381 public static final int AUDIO_LOCATION_FRONT_RIGHT_OF_CENTER = 0x01 << 7; 382 383 /** 384 * This represents an audio location back center. 385 * 386 * @hide 387 */ 388 @SystemApi 389 public static final int AUDIO_LOCATION_BACK_CENTER = 0x01 << 8; 390 391 /** 392 * This represents an audio location low frequency effects 2. 393 * 394 * @hide 395 */ 396 @SystemApi 397 public static final int AUDIO_LOCATION_LOW_FREQ_EFFECTS_TWO = 0x01 << 9; 398 399 /** 400 * This represents an audio location side left. 401 * 402 * @hide 403 */ 404 @SystemApi 405 public static final int AUDIO_LOCATION_SIDE_LEFT = 0x01 << 10; 406 407 /** 408 * This represents an audio location side right. 409 * 410 * @hide 411 */ 412 @SystemApi 413 public static final int AUDIO_LOCATION_SIDE_RIGHT = 0x01 << 11; 414 415 /** 416 * This represents an audio location top front left. 417 * 418 * @hide 419 */ 420 @SystemApi 421 public static final int AUDIO_LOCATION_TOP_FRONT_LEFT = 0x01 << 12; 422 423 /** 424 * This represents an audio location top front right. 425 * 426 * @hide 427 */ 428 @SystemApi 429 public static final int AUDIO_LOCATION_TOP_FRONT_RIGHT = 0x01 << 13; 430 431 /** 432 * This represents an audio location top front center. 433 * 434 * @hide 435 */ 436 @SystemApi 437 public static final int AUDIO_LOCATION_TOP_FRONT_CENTER = 0x01 << 14; 438 439 /** 440 * This represents an audio location top center. 441 * 442 * @hide 443 */ 444 @SystemApi 445 public static final int AUDIO_LOCATION_TOP_CENTER = 0x01 << 15; 446 447 /** 448 * This represents an audio location top back left. 449 * 450 * @hide 451 */ 452 @SystemApi 453 public static final int AUDIO_LOCATION_TOP_BACK_LEFT = 0x01 << 16; 454 455 /** 456 * This represents an audio location top back right. 457 * 458 * @hide 459 */ 460 @SystemApi 461 public static final int AUDIO_LOCATION_TOP_BACK_RIGHT = 0x01 << 17; 462 463 /** 464 * This represents an audio location top side left. 465 * 466 * @hide 467 */ 468 @SystemApi 469 public static final int AUDIO_LOCATION_TOP_SIDE_LEFT = 0x01 << 18; 470 471 /** 472 * This represents an audio location top side right. 473 * 474 * @hide 475 */ 476 @SystemApi 477 public static final int AUDIO_LOCATION_TOP_SIDE_RIGHT = 0x01 << 19; 478 479 /** 480 * This represents an audio location top back center. 481 * 482 * @hide 483 */ 484 @SystemApi 485 public static final int AUDIO_LOCATION_TOP_BACK_CENTER = 0x01 << 20; 486 487 /** 488 * This represents an audio location bottom front center. 489 * 490 * @hide 491 */ 492 @SystemApi 493 public static final int AUDIO_LOCATION_BOTTOM_FRONT_CENTER = 0x01 << 21; 494 495 /** 496 * This represents an audio location bottom front left. 497 * 498 * @hide 499 */ 500 @SystemApi 501 public static final int AUDIO_LOCATION_BOTTOM_FRONT_LEFT = 0x01 << 22; 502 503 /** 504 * This represents an audio location bottom front right. 505 * 506 * @hide 507 */ 508 @SystemApi 509 public static final int AUDIO_LOCATION_BOTTOM_FRONT_RIGHT = 0x01 << 23; 510 511 /** 512 * This represents an audio location front left wide. 513 * 514 * @hide 515 */ 516 @SystemApi 517 public static final int AUDIO_LOCATION_FRONT_LEFT_WIDE = 0x01 << 24; 518 519 /** 520 * This represents an audio location front right wide. 521 * 522 * @hide 523 */ 524 @SystemApi 525 public static final int AUDIO_LOCATION_FRONT_RIGHT_WIDE = 0x01 << 25; 526 527 /** 528 * This represents an audio location left surround. 529 * 530 * @hide 531 */ 532 @SystemApi 533 public static final int AUDIO_LOCATION_LEFT_SURROUND = 0x01 << 26; 534 535 /** 536 * This represents an audio location right surround. 537 * 538 * @hide 539 */ 540 @SystemApi 541 public static final int AUDIO_LOCATION_RIGHT_SURROUND = 0x01 << 27; 542 543 /** @hide */ 544 @IntDef(flag = true, prefix = "AUDIO_LOCATION_", 545 value = { 546 AUDIO_LOCATION_FRONT_LEFT, 547 AUDIO_LOCATION_FRONT_RIGHT, 548 AUDIO_LOCATION_FRONT_CENTER, 549 AUDIO_LOCATION_LOW_FREQ_EFFECTS_ONE, 550 AUDIO_LOCATION_BACK_LEFT, 551 AUDIO_LOCATION_BACK_RIGHT, 552 AUDIO_LOCATION_FRONT_LEFT_OF_CENTER, 553 AUDIO_LOCATION_FRONT_RIGHT_OF_CENTER, 554 AUDIO_LOCATION_BACK_CENTER, 555 AUDIO_LOCATION_LOW_FREQ_EFFECTS_TWO, 556 AUDIO_LOCATION_SIDE_LEFT, 557 AUDIO_LOCATION_SIDE_RIGHT, 558 AUDIO_LOCATION_TOP_FRONT_LEFT, 559 AUDIO_LOCATION_TOP_FRONT_RIGHT, 560 AUDIO_LOCATION_TOP_FRONT_CENTER, 561 AUDIO_LOCATION_TOP_CENTER, 562 AUDIO_LOCATION_TOP_BACK_LEFT, 563 AUDIO_LOCATION_TOP_BACK_RIGHT, 564 AUDIO_LOCATION_TOP_SIDE_LEFT, 565 AUDIO_LOCATION_TOP_SIDE_RIGHT, 566 AUDIO_LOCATION_TOP_BACK_CENTER, 567 AUDIO_LOCATION_BOTTOM_FRONT_CENTER, 568 AUDIO_LOCATION_BOTTOM_FRONT_LEFT, 569 AUDIO_LOCATION_BOTTOM_FRONT_RIGHT, 570 AUDIO_LOCATION_FRONT_LEFT_WIDE, 571 AUDIO_LOCATION_FRONT_RIGHT_WIDE, 572 AUDIO_LOCATION_LEFT_SURROUND, 573 AUDIO_LOCATION_RIGHT_SURROUND, 574 }) 575 @Retention(RetentionPolicy.SOURCE) 576 public @interface AudioLocation {} 577 578 /** 579 * Contains group id. 580 * @hide 581 */ 582 @SystemApi 583 public static final String EXTRA_LE_AUDIO_GROUP_ID = 584 "android.bluetooth.extra.LE_AUDIO_GROUP_ID"; 585 586 /** 587 * Contains bit mask for direction, bit 0 set when Sink, bit 1 set when Source. 588 * @hide 589 */ 590 public static final String EXTRA_LE_AUDIO_DIRECTION = 591 "android.bluetooth.extra.LE_AUDIO_DIRECTION"; 592 593 /** 594 * Contains source location as per Bluetooth Assigned Numbers 595 * @hide 596 */ 597 public static final String EXTRA_LE_AUDIO_SOURCE_LOCATION = 598 "android.bluetooth.extra.LE_AUDIO_SOURCE_LOCATION"; 599 600 /** 601 * Contains sink location as per Bluetooth Assigned Numbers 602 * @hide 603 */ 604 public static final String EXTRA_LE_AUDIO_SINK_LOCATION = 605 "android.bluetooth.extra.LE_AUDIO_SINK_LOCATION"; 606 607 /** 608 * Contains available context types for group as per Bluetooth Assigned Numbers 609 * @hide 610 */ 611 public static final String EXTRA_LE_AUDIO_AVAILABLE_CONTEXTS = 612 "android.bluetooth.extra.LE_AUDIO_AVAILABLE_CONTEXTS"; 613 614 private final BluetoothAdapter mAdapter; 615 private final AttributionSource mAttributionSource; 616 /** 617 * Indicating that group is Active ( Audio device is available ) 618 * @hide 619 */ 620 public static final int GROUP_STATUS_ACTIVE = IBluetoothLeAudio.GROUP_STATUS_ACTIVE; 621 622 /** 623 * Indicating that group is Inactive ( Audio device is not available ) 624 * @hide 625 */ 626 public static final int GROUP_STATUS_INACTIVE = IBluetoothLeAudio.GROUP_STATUS_INACTIVE; 627 628 private final BluetoothProfileConnector<IBluetoothLeAudio> mProfileConnector = 629 new BluetoothProfileConnector(this, BluetoothProfile.LE_AUDIO, "BluetoothLeAudio", 630 IBluetoothLeAudio.class.getName()) { 631 @Override 632 public IBluetoothLeAudio getServiceInterface(IBinder service) { 633 return IBluetoothLeAudio.Stub.asInterface(service); 634 } 635 }; 636 637 638 @SuppressLint("AndroidFrameworkBluetoothPermission") 639 private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = 640 new IBluetoothStateChangeCallback.Stub() { 641 public void onBluetoothStateChange(boolean up) { 642 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); 643 if (up) { 644 // re-register the service-to-app callback 645 synchronized (mCallbackExecutorMap) { 646 if (!mCallbackExecutorMap.isEmpty()) { 647 try { 648 final IBluetoothLeAudio service = getService(); 649 if (service != null) { 650 final SynchronousResultReceiver<Integer> recv = 651 SynchronousResultReceiver.get(); 652 service.registerCallback(mCallback, 653 mAttributionSource, recv); 654 recv.awaitResultNoInterrupt(getSyncTimeout()) 655 .getValue(null); 656 } 657 } catch (TimeoutException e) { 658 Log.e(TAG, "Failed to register callback", e); 659 } catch (RemoteException e) { 660 throw e.rethrowFromSystemServer(); 661 } 662 } 663 } 664 } 665 } 666 }; 667 668 /** 669 * Create a BluetoothLeAudio proxy object for interacting with the local 670 * Bluetooth LeAudio service. 671 */ BluetoothLeAudio(Context context, ServiceListener listener, BluetoothAdapter adapter)672 /* package */ BluetoothLeAudio(Context context, ServiceListener listener, 673 BluetoothAdapter adapter) { 674 mAdapter = adapter; 675 mAttributionSource = adapter.getAttributionSource(); 676 mProfileConnector.connect(context, listener); 677 678 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 679 if (mgr != null) { 680 try { 681 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); 682 } catch (RemoteException e) { 683 throw e.rethrowFromSystemServer(); 684 } 685 } 686 687 mCloseGuard = new CloseGuard(); 688 mCloseGuard.open("close"); 689 } 690 691 /** 692 * @hide 693 */ close()694 public void close() { 695 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 696 if (mgr != null) { 697 try { 698 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); 699 } catch (RemoteException e) { 700 Log.e(TAG, "", e); 701 } 702 } 703 704 mProfileConnector.disconnect(); 705 } 706 getService()707 private IBluetoothLeAudio getService() { 708 return mProfileConnector.getService(); 709 } 710 finalize()711 protected void finalize() { 712 if (mCloseGuard != null) { 713 mCloseGuard.warnIfOpen(); 714 } 715 close(); 716 } 717 718 /** 719 * Initiate connection to a profile of the remote bluetooth device. 720 * 721 * <p> This API returns false in scenarios like the profile on the 722 * device is already connected or Bluetooth is not turned on. 723 * When this API returns true, it is guaranteed that 724 * connection state intent for the profile will be broadcasted with 725 * the state. Users can get the connection state of the profile 726 * from this intent. 727 * 728 * 729 * @param device Remote Bluetooth Device 730 * @return false on immediate error, true otherwise 731 * @hide 732 */ 733 @RequiresBluetoothConnectPermission 734 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) connect(@ullable BluetoothDevice device)735 public boolean connect(@Nullable BluetoothDevice device) { 736 if (DBG) log("connect(" + device + ")"); 737 final IBluetoothLeAudio service = getService(); 738 final boolean defaultValue = false; 739 if (service == null) { 740 Log.w(TAG, "Proxy not attached to service"); 741 if (DBG) log(Log.getStackTraceString(new Throwable())); 742 } else if (mAdapter.isEnabled() && isValidDevice(device)) { 743 try { 744 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 745 service.connect(device, mAttributionSource, recv); 746 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 747 } catch (RemoteException | TimeoutException e) { 748 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 749 } 750 } 751 return defaultValue; 752 } 753 754 /** 755 * Initiate disconnection from a profile 756 * 757 * <p> This API will return false in scenarios like the profile on the 758 * Bluetooth device is not in connected state etc. When this API returns, 759 * true, it is guaranteed that the connection state change 760 * intent will be broadcasted with the state. Users can get the 761 * disconnection state of the profile from this intent. 762 * 763 * <p> If the disconnection is initiated by a remote device, the state 764 * will transition from {@link #STATE_CONNECTED} to 765 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 766 * host (local) device the state will transition from 767 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 768 * state {@link #STATE_DISCONNECTED}. The transition to 769 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 770 * two scenarios. 771 * 772 * 773 * @param device Remote Bluetooth Device 774 * @return false on immediate error, true otherwise 775 * @hide 776 */ 777 @RequiresBluetoothConnectPermission 778 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) disconnect(@ullable BluetoothDevice device)779 public boolean disconnect(@Nullable BluetoothDevice device) { 780 if (DBG) log("disconnect(" + device + ")"); 781 final IBluetoothLeAudio service = getService(); 782 final boolean defaultValue = false; 783 if (service == null) { 784 Log.w(TAG, "Proxy not attached to service"); 785 if (DBG) log(Log.getStackTraceString(new Throwable())); 786 } else if (mAdapter.isEnabled() && isValidDevice(device)) { 787 try { 788 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 789 service.disconnect(device, mAttributionSource, recv); 790 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 791 } catch (RemoteException | TimeoutException e) { 792 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 793 } 794 } 795 return defaultValue; 796 } 797 798 /** 799 * Get Lead device for the group. 800 * 801 * Lead device is the device that can be used as an active device in the system. 802 * Active devices points to the Audio Device for the Le Audio group. 803 * This method returns the Lead devices for the connected LE Audio 804 * group and this device should be used in the setActiveDevice() method by other parts 805 * of the system, which wants to set to active a particular Le Audio group. 806 * 807 * Note: getActiveDevice() returns the Lead device for the currently active LE Audio group. 808 * Note: When Lead device gets disconnected while Le Audio group is active and has more devices 809 * in the group, then Lead device will not change. If Lead device gets disconnected, for the 810 * Le Audio group which is not active, a new Lead device will be chosen 811 * 812 * @param groupId The group id. 813 * @return group lead device. 814 */ 815 @RequiresBluetoothConnectPermission 816 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectedGroupLeadDevice(int groupId)817 public @Nullable BluetoothDevice getConnectedGroupLeadDevice(int groupId) { 818 if (VDBG) log("getConnectedGroupLeadDevice()"); 819 final IBluetoothLeAudio service = getService(); 820 final BluetoothDevice defaultValue = null; 821 if (service == null) { 822 Log.w(TAG, "Proxy not attached to service"); 823 if (DBG) log(Log.getStackTraceString(new Throwable())); 824 } else if (mAdapter.isEnabled()) { 825 try { 826 final SynchronousResultReceiver<BluetoothDevice> recv = 827 SynchronousResultReceiver.get(); 828 service.getConnectedGroupLeadDevice(groupId, mAttributionSource, recv); 829 return Attributable.setAttributionSource( 830 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), 831 mAttributionSource); 832 } catch (RemoteException | TimeoutException e) { 833 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 834 } 835 } 836 return defaultValue; 837 } 838 839 /** 840 * {@inheritDoc} 841 */ 842 @Override 843 @RequiresBluetoothConnectPermission 844 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectedDevices()845 public @NonNull List<BluetoothDevice> getConnectedDevices() { 846 if (VDBG) log("getConnectedDevices()"); 847 final IBluetoothLeAudio service = getService(); 848 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); 849 if (service == null) { 850 Log.w(TAG, "Proxy not attached to service"); 851 if (DBG) log(Log.getStackTraceString(new Throwable())); 852 } else if (mAdapter.isEnabled()) { 853 try { 854 final SynchronousResultReceiver<List<BluetoothDevice>> recv = 855 SynchronousResultReceiver.get(); 856 service.getConnectedDevices(mAttributionSource, recv); 857 return Attributable.setAttributionSource( 858 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), 859 mAttributionSource); 860 } catch (RemoteException | TimeoutException e) { 861 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 862 } 863 } 864 return defaultValue; 865 } 866 867 /** 868 * {@inheritDoc} 869 */ 870 @Override 871 @RequiresBluetoothConnectPermission 872 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getDevicesMatchingConnectionStates( @onNull int[] states)873 public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( 874 @NonNull int[] states) { 875 if (VDBG) log("getDevicesMatchingStates()"); 876 final IBluetoothLeAudio service = getService(); 877 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); 878 if (service == null) { 879 Log.w(TAG, "Proxy not attached to service"); 880 if (DBG) log(Log.getStackTraceString(new Throwable())); 881 } else if (mAdapter.isEnabled()) { 882 try { 883 final SynchronousResultReceiver<List<BluetoothDevice>> recv = 884 SynchronousResultReceiver.get(); 885 service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); 886 return Attributable.setAttributionSource( 887 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), 888 mAttributionSource); 889 } catch (RemoteException | TimeoutException e) { 890 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 891 } 892 } 893 return defaultValue; 894 } 895 896 /** 897 * {@inheritDoc} 898 */ 899 @Override 900 @RequiresLegacyBluetoothPermission 901 @RequiresBluetoothConnectPermission 902 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectionState(@onNull BluetoothDevice device)903 public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) { 904 if (VDBG) log("getState(" + device + ")"); 905 final IBluetoothLeAudio service = getService(); 906 final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; 907 if (service == null) { 908 Log.w(TAG, "Proxy not attached to service"); 909 if (DBG) log(Log.getStackTraceString(new Throwable())); 910 } else if (mAdapter.isEnabled() && isValidDevice(device)) { 911 try { 912 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); 913 service.getConnectionState(device, mAttributionSource, recv); 914 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 915 } catch (RemoteException | TimeoutException e) { 916 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 917 } 918 } 919 return defaultValue; 920 } 921 922 /** 923 * Register a {@link Callback} that will be invoked during the 924 * operation of this profile. 925 * 926 * Repeated registration of the same <var>callback</var> object will have no effect after 927 * the first call to this method, even when the <var>executor</var> is different. API caller 928 * would have to call {@link #unregisterCallback(Callback)} with 929 * the same callback object before registering it again. 930 * 931 * <p> The {@link Callback} will be invoked only if there is codec status changed for the 932 * remote device or the device is connected/disconnected in a certain group or the group 933 * status is changed. 934 * 935 * @param executor an {@link Executor} to execute given callback 936 * @param callback user implementation of the {@link Callback} 937 * @throws NullPointerException if a null executor or callback is given 938 * @throws IllegalArgumentException the callback is already registered 939 * @hide 940 */ 941 @SystemApi 942 @RequiresBluetoothConnectPermission 943 @RequiresPermission(allOf = { 944 android.Manifest.permission.BLUETOOTH_CONNECT, 945 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 946 }) registerCallback(@onNull @allbackExecutor Executor executor, @NonNull Callback callback)947 public void registerCallback(@NonNull @CallbackExecutor Executor executor, 948 @NonNull Callback callback) { 949 Objects.requireNonNull(executor, "executor cannot be null"); 950 Objects.requireNonNull(callback, "callback cannot be null"); 951 if (DBG) log("registerCallback"); 952 953 synchronized (mCallbackExecutorMap) { 954 // If the callback map is empty, we register the service-to-app callback 955 if (mCallbackExecutorMap.isEmpty()) { 956 if (!mAdapter.isEnabled()) { 957 /* If Bluetooth is off, just store callback and it will be registered 958 * when Bluetooth is on 959 */ 960 mCallbackExecutorMap.put(callback, executor); 961 return; 962 } 963 try { 964 final IBluetoothLeAudio service = getService(); 965 if (service != null) { 966 final SynchronousResultReceiver<Integer> recv = 967 SynchronousResultReceiver.get(); 968 service.registerCallback(mCallback, mAttributionSource, recv); 969 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); 970 } 971 } catch (TimeoutException e) { 972 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 973 } catch (RemoteException e) { 974 throw e.rethrowFromSystemServer(); 975 } 976 } 977 978 // Adds the passed in callback to our map of callbacks to executors 979 if (mCallbackExecutorMap.containsKey(callback)) { 980 throw new IllegalArgumentException("This callback has already been registered"); 981 } 982 mCallbackExecutorMap.put(callback, executor); 983 } 984 } 985 986 /** 987 * Unregister the specified {@link Callback}. 988 * <p>The same {@link Callback} object used when calling 989 * {@link #registerCallback(Executor, Callback)} must be used. 990 * 991 * <p>Callbacks are automatically unregistered when application process goes away 992 * 993 * @param callback user implementation of the {@link Callback} 994 * @throws NullPointerException when callback is null 995 * @throws IllegalArgumentException when no callback is registered 996 * @hide 997 */ 998 @SystemApi 999 @RequiresBluetoothConnectPermission 1000 @RequiresPermission(allOf = { 1001 android.Manifest.permission.BLUETOOTH_CONNECT, 1002 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 1003 }) unregisterCallback(@onNull Callback callback)1004 public void unregisterCallback(@NonNull Callback callback) { 1005 Objects.requireNonNull(callback, "callback cannot be null"); 1006 if (DBG) log("unregisterCallback"); 1007 1008 synchronized (mCallbackExecutorMap) { 1009 if (mCallbackExecutorMap.remove(callback) == null) { 1010 throw new IllegalArgumentException("This callback has not been registered"); 1011 } 1012 } 1013 1014 // If the callback map is empty, we unregister the service-to-app callback 1015 if (mCallbackExecutorMap.isEmpty()) { 1016 try { 1017 final IBluetoothLeAudio service = getService(); 1018 if (service != null) { 1019 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); 1020 service.unregisterCallback(mCallback, mAttributionSource, recv); 1021 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); 1022 } 1023 } catch (TimeoutException e) { 1024 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1025 } catch (RemoteException e) { 1026 throw e.rethrowFromSystemServer(); 1027 } 1028 } 1029 } 1030 1031 /** 1032 * Select a connected device as active. 1033 * 1034 * The active device selection is per profile. An active device's 1035 * purpose is profile-specific. For example, LeAudio audio 1036 * streaming is to the active LeAudio device. If a remote device 1037 * is not connected, it cannot be selected as active. 1038 * 1039 * <p> This API returns false in scenarios like the profile on the 1040 * device is not connected or Bluetooth is not turned on. 1041 * When this API returns true, it is guaranteed that the 1042 * {@link #ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED} intent will be broadcasted 1043 * with the active device. 1044 * 1045 * 1046 * @param device the remote Bluetooth device. Could be null to clear 1047 * the active device and stop streaming audio to a Bluetooth device. 1048 * @return false on immediate error, true otherwise 1049 * @hide 1050 */ 1051 @RequiresBluetoothConnectPermission 1052 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) setActiveDevice(@ullable BluetoothDevice device)1053 public boolean setActiveDevice(@Nullable BluetoothDevice device) { 1054 if (DBG) log("setActiveDevice(" + device + ")"); 1055 final IBluetoothLeAudio service = getService(); 1056 final boolean defaultValue = false; 1057 if (service == null) { 1058 Log.w(TAG, "Proxy not attached to service"); 1059 if (DBG) log(Log.getStackTraceString(new Throwable())); 1060 } else if (mAdapter.isEnabled() && ((device == null) || isValidDevice(device))) { 1061 try { 1062 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 1063 service.setActiveDevice(device, mAttributionSource, recv); 1064 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 1065 } catch (RemoteException | TimeoutException e) { 1066 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1067 } 1068 } 1069 return defaultValue; 1070 } 1071 1072 /** 1073 * Get the connected LeAudio devices that are active 1074 * 1075 * @return the list of active devices. Returns empty list on error. 1076 * @hide 1077 */ 1078 @NonNull 1079 @RequiresLegacyBluetoothPermission 1080 @RequiresBluetoothConnectPermission 1081 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getActiveDevices()1082 public List<BluetoothDevice> getActiveDevices() { 1083 if (VDBG) log("getActiveDevice()"); 1084 final IBluetoothLeAudio service = getService(); 1085 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); 1086 if (service == null) { 1087 Log.w(TAG, "Proxy not attached to service"); 1088 if (DBG) log(Log.getStackTraceString(new Throwable())); 1089 } else if (mAdapter.isEnabled()) { 1090 try { 1091 final SynchronousResultReceiver<List<BluetoothDevice>> recv = 1092 SynchronousResultReceiver.get(); 1093 service.getActiveDevices(mAttributionSource, recv); 1094 return Attributable.setAttributionSource( 1095 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), 1096 mAttributionSource); 1097 } catch (RemoteException | TimeoutException e) { 1098 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1099 } 1100 } 1101 return defaultValue; 1102 } 1103 1104 /** 1105 * Get device group id. Devices with same group id belong to same group (i.e left and right 1106 * earbud) 1107 * @param device LE Audio capable device 1108 * @return group id that this device currently belongs to, {@link #GROUP_ID_INVALID} when this 1109 * device does not belong to any group 1110 */ 1111 @RequiresLegacyBluetoothPermission 1112 @RequiresBluetoothConnectPermission 1113 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getGroupId(@onNull BluetoothDevice device)1114 public int getGroupId(@NonNull BluetoothDevice device) { 1115 if (VDBG) log("getGroupId()"); 1116 final IBluetoothLeAudio service = getService(); 1117 final int defaultValue = GROUP_ID_INVALID; 1118 if (service == null) { 1119 Log.w(TAG, "Proxy not attached to service"); 1120 if (DBG) log(Log.getStackTraceString(new Throwable())); 1121 } else if (mAdapter.isEnabled()) { 1122 try { 1123 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); 1124 service.getGroupId(device, mAttributionSource, recv); 1125 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 1126 } catch (RemoteException | TimeoutException e) { 1127 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1128 } 1129 } 1130 return defaultValue; 1131 } 1132 1133 /** 1134 * Set volume for the streaming devices 1135 * 1136 * @param volume volume to set 1137 * @hide 1138 */ 1139 @SystemApi 1140 @RequiresBluetoothConnectPermission 1141 @RequiresPermission(allOf = { 1142 android.Manifest.permission.BLUETOOTH_CONNECT, 1143 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1144 }) setVolume(@ntRangefrom = 0, to = 255) int volume)1145 public void setVolume(@IntRange(from = 0, to = 255) int volume) { 1146 if (VDBG) log("setVolume(vol: " + volume + " )"); 1147 final IBluetoothLeAudio service = getService(); 1148 if (service == null) { 1149 Log.w(TAG, "Proxy not attached to service"); 1150 if (DBG) log(Log.getStackTraceString(new Throwable())); 1151 } else if (mAdapter.isEnabled()) { 1152 try { 1153 final SynchronousResultReceiver recv = SynchronousResultReceiver.get(); 1154 service.setVolume(volume, mAttributionSource, recv); 1155 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); 1156 } catch (RemoteException | TimeoutException e) { 1157 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1158 } 1159 } 1160 } 1161 1162 /** 1163 * Add device to the given group. 1164 * @param group_id group ID the device is being added to 1165 * @param device the active device 1166 * @return true on success, otherwise false 1167 * @hide 1168 */ 1169 @RequiresBluetoothConnectPermission 1170 @RequiresPermission(allOf = { 1171 android.Manifest.permission.BLUETOOTH_CONNECT, 1172 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1173 }) groupAddNode(int group_id, @NonNull BluetoothDevice device)1174 public boolean groupAddNode(int group_id, @NonNull BluetoothDevice device) { 1175 if (VDBG) log("groupAddNode()"); 1176 final IBluetoothLeAudio service = getService(); 1177 final boolean defaultValue = false; 1178 if (service == null) { 1179 Log.w(TAG, "Proxy not attached to service"); 1180 if (DBG) log(Log.getStackTraceString(new Throwable())); 1181 } else if (mAdapter.isEnabled()) { 1182 try { 1183 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 1184 service.groupAddNode(group_id, device, mAttributionSource, recv); 1185 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 1186 } catch (RemoteException | TimeoutException e) { 1187 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1188 } 1189 } 1190 return defaultValue; 1191 } 1192 1193 /** 1194 * Remove device from a given group. 1195 * @param group_id group ID the device is being removed from 1196 * @param device the active device 1197 * @return true on success, otherwise false 1198 * 1199 * @hide 1200 */ 1201 @RequiresBluetoothConnectPermission 1202 @RequiresPermission(allOf = { 1203 android.Manifest.permission.BLUETOOTH_CONNECT, 1204 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1205 }) groupRemoveNode(int group_id, @NonNull BluetoothDevice device)1206 public boolean groupRemoveNode(int group_id, @NonNull BluetoothDevice device) { 1207 if (VDBG) log("groupRemoveNode()"); 1208 final IBluetoothLeAudio service = getService(); 1209 final boolean defaultValue = false; 1210 if (service == null) { 1211 Log.w(TAG, "Proxy not attached to service"); 1212 if (DBG) log(Log.getStackTraceString(new Throwable())); 1213 } else if (mAdapter.isEnabled()) { 1214 try { 1215 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 1216 service.groupRemoveNode(group_id, device, mAttributionSource, recv); 1217 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 1218 } catch (RemoteException | TimeoutException e) { 1219 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1220 } 1221 } 1222 return defaultValue; 1223 } 1224 1225 /** 1226 * Get the audio location for the device. The return value is a bit field. The bit definition 1227 * is included in Bluetooth SIG Assigned Numbers - Generic Audio - Audio Location Definitions. 1228 * ex. Front Left: 0x00000001 1229 * Front Right: 0x00000002 1230 * Front Left | Front Right: 0x00000003 1231 * 1232 * @param device the bluetooth device 1233 * @return The bit field of audio location for the device, if bluetooth is off, return 1234 * AUDIO_LOCATION_INVALID. 1235 * 1236 * @hide 1237 */ 1238 @RequiresBluetoothConnectPermission 1239 @RequiresPermission(allOf = { 1240 android.Manifest.permission.BLUETOOTH_CONNECT, 1241 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1242 }) 1243 @SystemApi getAudioLocation(@onNull BluetoothDevice device)1244 public @AudioLocation int getAudioLocation(@NonNull BluetoothDevice device) { 1245 if (VDBG) log("getAudioLocation()"); 1246 final IBluetoothLeAudio service = getService(); 1247 final int defaultLocation = AUDIO_LOCATION_INVALID; 1248 if (service == null) { 1249 Log.w(TAG, "Proxy not attached to service"); 1250 if (DBG) log(Log.getStackTraceString(new Throwable())); 1251 } else if (mAdapter.isEnabled() && isValidDevice(device)) { 1252 try { 1253 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); 1254 service.getAudioLocation(device, mAttributionSource, recv); 1255 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultLocation); 1256 } catch (RemoteException | TimeoutException e) { 1257 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1258 } 1259 } 1260 return defaultLocation; 1261 } 1262 1263 /** 1264 * Set connection policy of the profile 1265 * 1266 * <p> The device should already be paired. 1267 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, 1268 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 1269 * 1270 * @param device Paired bluetooth device 1271 * @param connectionPolicy is the connection policy to set to for this profile 1272 * @return true if connectionPolicy is set, false on error 1273 * @hide 1274 */ 1275 @SystemApi 1276 @RequiresBluetoothConnectPermission 1277 @RequiresPermission(allOf = { 1278 android.Manifest.permission.BLUETOOTH_CONNECT, 1279 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 1280 }) setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)1281 public boolean setConnectionPolicy(@NonNull BluetoothDevice device, 1282 @ConnectionPolicy int connectionPolicy) { 1283 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 1284 final IBluetoothLeAudio service = getService(); 1285 final boolean defaultValue = false; 1286 if (service == null) { 1287 Log.w(TAG, "Proxy not attached to service"); 1288 if (DBG) log(Log.getStackTraceString(new Throwable())); 1289 } else if (mAdapter.isEnabled() && isValidDevice(device) 1290 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 1291 || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { 1292 try { 1293 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 1294 service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); 1295 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 1296 } catch (RemoteException | TimeoutException e) { 1297 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1298 } 1299 } 1300 return defaultValue; 1301 } 1302 1303 /** 1304 * Get the connection policy of the profile. 1305 * 1306 * <p> The connection policy can be any of: 1307 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, 1308 * {@link #CONNECTION_POLICY_UNKNOWN} 1309 * 1310 * @param device Bluetooth device 1311 * @return connection policy of the device 1312 * @hide 1313 */ 1314 @SystemApi 1315 @RequiresBluetoothConnectPermission 1316 @RequiresPermission(allOf = { 1317 android.Manifest.permission.BLUETOOTH_CONNECT, 1318 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1319 }) getConnectionPolicy(@ullable BluetoothDevice device)1320 public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) { 1321 if (VDBG) log("getConnectionPolicy(" + device + ")"); 1322 final IBluetoothLeAudio service = getService(); 1323 final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 1324 if (service == null) { 1325 Log.w(TAG, "Proxy not attached to service"); 1326 if (DBG) log(Log.getStackTraceString(new Throwable())); 1327 } else if (mAdapter.isEnabled() && isValidDevice(device)) { 1328 try { 1329 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); 1330 service.getConnectionPolicy(device, mAttributionSource, recv); 1331 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 1332 } catch (RemoteException | TimeoutException e) { 1333 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1334 } 1335 } 1336 return defaultValue; 1337 } 1338 1339 1340 /** 1341 * Helper for converting a state to a string. 1342 * 1343 * For debug use only - strings are not internationalized. 1344 * 1345 * @hide 1346 */ stateToString(int state)1347 public static String stateToString(int state) { 1348 switch (state) { 1349 case STATE_DISCONNECTED: 1350 return "disconnected"; 1351 case STATE_CONNECTING: 1352 return "connecting"; 1353 case STATE_CONNECTED: 1354 return "connected"; 1355 case STATE_DISCONNECTING: 1356 return "disconnecting"; 1357 default: 1358 return "<unknown state " + state + ">"; 1359 } 1360 } 1361 isValidDevice(@ullable BluetoothDevice device)1362 private boolean isValidDevice(@Nullable BluetoothDevice device) { 1363 if (device == null) return false; 1364 1365 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 1366 return false; 1367 } 1368 log(String msg)1369 private static void log(String msg) { 1370 Log.d(TAG, msg); 1371 } 1372 1373 /** 1374 * Gets the current codec status (configuration and capability). 1375 * 1376 * @param groupId The group id 1377 * @return the current codec status 1378 * @hide 1379 */ 1380 @SystemApi 1381 @Nullable 1382 @RequiresBluetoothConnectPermission 1383 @RequiresPermission(allOf = { 1384 android.Manifest.permission.BLUETOOTH_CONNECT, 1385 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1386 }) getCodecStatus(int groupId)1387 public BluetoothLeAudioCodecStatus getCodecStatus(int groupId) { 1388 if (DBG) { 1389 Log.d(TAG, "getCodecStatus(" + groupId + ")"); 1390 } 1391 1392 final IBluetoothLeAudio service = getService(); 1393 final BluetoothLeAudioCodecStatus defaultValue = null; 1394 1395 if (service == null) { 1396 Log.w(TAG, "Proxy not attached to service"); 1397 if (DBG) log(Log.getStackTraceString(new Throwable())); 1398 } else if (mAdapter.isEnabled()) { 1399 try { 1400 final SynchronousResultReceiver<BluetoothLeAudioCodecStatus> recv = 1401 SynchronousResultReceiver.get(); 1402 service.getCodecStatus(groupId, mAttributionSource, recv); 1403 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 1404 } catch (TimeoutException e) { 1405 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1406 } catch (RemoteException e) { 1407 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1408 e.rethrowFromSystemServer(); 1409 } 1410 } 1411 return defaultValue; 1412 } 1413 1414 /** 1415 * Sets the codec configuration preference. 1416 * 1417 * @param groupId the groupId 1418 * @param inputCodecConfig the input codec configuration preference 1419 * @param outputCodecConfig the output codec configuration preference 1420 * @throws IllegalStateException if LE Audio Service is null 1421 * @throws NullPointerException if any of the configs is null 1422 * @hide 1423 */ 1424 @SystemApi 1425 @RequiresBluetoothConnectPermission 1426 @RequiresPermission(allOf = { 1427 android.Manifest.permission.BLUETOOTH_CONNECT, 1428 android.Manifest.permission.BLUETOOTH_PRIVILEGED 1429 }) setCodecConfigPreference(int groupId, @NonNull BluetoothLeAudioCodecConfig inputCodecConfig, @NonNull BluetoothLeAudioCodecConfig outputCodecConfig)1430 public void setCodecConfigPreference(int groupId, 1431 @NonNull BluetoothLeAudioCodecConfig inputCodecConfig, 1432 @NonNull BluetoothLeAudioCodecConfig outputCodecConfig) { 1433 if (DBG) Log.d(TAG, "setCodecConfigPreference(" + groupId + ")"); 1434 1435 Objects.requireNonNull(inputCodecConfig, " inputCodecConfig shall not be null"); 1436 Objects.requireNonNull(outputCodecConfig, " outputCodecConfig shall not be null"); 1437 1438 final IBluetoothLeAudio service = getService(); 1439 1440 if (service == null) { 1441 Log.w(TAG, "Proxy not attached to service"); 1442 if (DBG) log(Log.getStackTraceString(new Throwable())); 1443 throw new IllegalStateException("Service is unavailable"); 1444 } else if (mAdapter.isEnabled()) { 1445 try { 1446 service.setCodecConfigPreference(groupId, inputCodecConfig, outputCodecConfig, 1447 mAttributionSource); 1448 } catch (RemoteException e) { 1449 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1450 e.rethrowFromSystemServer(); 1451 } 1452 } 1453 } 1454 1455 } 1456