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