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