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