1 /* 2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at: 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.bluetooth; 18 19 import android.annotation.CallbackExecutor; 20 import android.annotation.FlaggedApi; 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SdkConstant; 26 import android.annotation.SystemApi; 27 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 28 import android.bluetooth.annotations.RequiresBluetoothLocationPermission; 29 import android.bluetooth.annotations.RequiresBluetoothScanPermission; 30 import android.bluetooth.le.ScanFilter; 31 import android.bluetooth.le.ScanSettings; 32 import android.content.Context; 33 import android.os.IBinder; 34 import android.os.RemoteException; 35 import android.util.CloseGuard; 36 import android.util.Log; 37 38 import com.android.bluetooth.flags.Flags; 39 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.util.ArrayList; 43 import java.util.HashMap; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.Objects; 47 import java.util.concurrent.Executor; 48 49 /** 50 * This class provides the public APIs for the LE Audio Broadcast Assistant role, which implements 51 * client side control points for Broadcast Audio Scan Service (BASS). 52 * 53 * <p>An LE Audio Broadcast Assistant can help a Broadcast Sink to scan for available Broadcast 54 * Sources. The Broadcast Sink achieves this by offloading the scan to a Broadcast Assistant. This 55 * is facilitated by the Broadcast Audio Scan Service (BASS). A BASS server is a GATT server that is 56 * part of the Scan Delegator on a Broadcast Sink. A BASS client instead runs on the Broadcast 57 * Assistant. 58 * 59 * <p>Once a GATT connection is established between the BASS client and the BASS server, the 60 * Broadcast Sink can offload the scans to the Broadcast Assistant. Upon finding new Broadcast 61 * Sources, the Broadcast Assistant then notifies the Broadcast Sink about these over the 62 * established GATT connection. The Scan Delegator on the Broadcast Sink can also notify the 63 * Assistant about changes such as addition and removal of Broadcast Sources. 64 * 65 * <p>In the context of this class, BASS server will be addressed as Broadcast Sink and BASS client 66 * will be addressed as Broadcast Assistant. 67 * 68 * <p>BluetoothLeBroadcastAssistant is a proxy object for controlling the Broadcast Assistant 69 * service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the 70 * BluetoothLeBroadcastAssistant proxy object. 71 * 72 * @hide 73 */ 74 @SystemApi 75 public final class BluetoothLeBroadcastAssistant implements BluetoothProfile, AutoCloseable { 76 private static final String TAG = "BluetoothLeBroadcastAssistant"; 77 private static final boolean DBG = true; 78 private final Map<Callback, Executor> mCallbackExecutorMap = new HashMap<>(); 79 80 private final IBluetoothLeBroadcastAssistantCallback mCallback = 81 new IBluetoothLeBroadcastAssistantCallback.Stub() { 82 @Override 83 public void onSearchStarted(int reason) { 84 for (Map.Entry<BluetoothLeBroadcastAssistant.Callback, Executor> 85 callbackExecutorEntry : mCallbackExecutorMap.entrySet()) { 86 BluetoothLeBroadcastAssistant.Callback callback = 87 callbackExecutorEntry.getKey(); 88 Executor executor = callbackExecutorEntry.getValue(); 89 executor.execute(() -> callback.onSearchStarted(reason)); 90 } 91 } 92 93 @Override 94 public void onSearchStartFailed(int reason) { 95 for (Map.Entry<BluetoothLeBroadcastAssistant.Callback, Executor> 96 callbackExecutorEntry : mCallbackExecutorMap.entrySet()) { 97 BluetoothLeBroadcastAssistant.Callback callback = 98 callbackExecutorEntry.getKey(); 99 Executor executor = callbackExecutorEntry.getValue(); 100 executor.execute(() -> callback.onSearchStartFailed(reason)); 101 } 102 } 103 104 @Override 105 public void onSearchStopped(int reason) { 106 for (Map.Entry<BluetoothLeBroadcastAssistant.Callback, Executor> 107 callbackExecutorEntry : mCallbackExecutorMap.entrySet()) { 108 BluetoothLeBroadcastAssistant.Callback callback = 109 callbackExecutorEntry.getKey(); 110 Executor executor = callbackExecutorEntry.getValue(); 111 executor.execute(() -> callback.onSearchStopped(reason)); 112 } 113 } 114 115 @Override 116 public void onSearchStopFailed(int reason) { 117 for (Map.Entry<BluetoothLeBroadcastAssistant.Callback, Executor> 118 callbackExecutorEntry : mCallbackExecutorMap.entrySet()) { 119 BluetoothLeBroadcastAssistant.Callback callback = 120 callbackExecutorEntry.getKey(); 121 Executor executor = callbackExecutorEntry.getValue(); 122 executor.execute(() -> callback.onSearchStopFailed(reason)); 123 } 124 } 125 126 @Override 127 public void onSourceFound(BluetoothLeBroadcastMetadata source) { 128 for (Map.Entry<BluetoothLeBroadcastAssistant.Callback, Executor> 129 callbackExecutorEntry : mCallbackExecutorMap.entrySet()) { 130 BluetoothLeBroadcastAssistant.Callback callback = 131 callbackExecutorEntry.getKey(); 132 Executor executor = callbackExecutorEntry.getValue(); 133 executor.execute(() -> callback.onSourceFound(source)); 134 } 135 } 136 137 @Override 138 public void onSourceAdded(BluetoothDevice sink, int sourceId, int reason) { 139 for (Map.Entry<BluetoothLeBroadcastAssistant.Callback, Executor> 140 callbackExecutorEntry : mCallbackExecutorMap.entrySet()) { 141 BluetoothLeBroadcastAssistant.Callback callback = 142 callbackExecutorEntry.getKey(); 143 Executor executor = callbackExecutorEntry.getValue(); 144 executor.execute(() -> callback.onSourceAdded(sink, sourceId, reason)); 145 } 146 } 147 148 @Override 149 public void onSourceAddFailed( 150 BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) { 151 for (Map.Entry<BluetoothLeBroadcastAssistant.Callback, Executor> 152 callbackExecutorEntry : mCallbackExecutorMap.entrySet()) { 153 BluetoothLeBroadcastAssistant.Callback callback = 154 callbackExecutorEntry.getKey(); 155 Executor executor = callbackExecutorEntry.getValue(); 156 executor.execute(() -> callback.onSourceAddFailed(sink, source, reason)); 157 } 158 } 159 160 @Override 161 public void onSourceModified(BluetoothDevice sink, int sourceId, int reason) { 162 for (Map.Entry<BluetoothLeBroadcastAssistant.Callback, Executor> 163 callbackExecutorEntry : mCallbackExecutorMap.entrySet()) { 164 BluetoothLeBroadcastAssistant.Callback callback = 165 callbackExecutorEntry.getKey(); 166 Executor executor = callbackExecutorEntry.getValue(); 167 executor.execute(() -> callback.onSourceModified(sink, sourceId, reason)); 168 } 169 } 170 171 @Override 172 public void onSourceModifyFailed(BluetoothDevice sink, int sourceId, int reason) { 173 for (Map.Entry<BluetoothLeBroadcastAssistant.Callback, Executor> 174 callbackExecutorEntry : mCallbackExecutorMap.entrySet()) { 175 BluetoothLeBroadcastAssistant.Callback callback = 176 callbackExecutorEntry.getKey(); 177 Executor executor = callbackExecutorEntry.getValue(); 178 executor.execute( 179 () -> callback.onSourceModifyFailed(sink, sourceId, reason)); 180 } 181 } 182 183 @Override 184 public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) { 185 for (Map.Entry<BluetoothLeBroadcastAssistant.Callback, Executor> 186 callbackExecutorEntry : mCallbackExecutorMap.entrySet()) { 187 BluetoothLeBroadcastAssistant.Callback callback = 188 callbackExecutorEntry.getKey(); 189 Executor executor = callbackExecutorEntry.getValue(); 190 executor.execute(() -> callback.onSourceRemoved(sink, sourceId, reason)); 191 } 192 } 193 194 @Override 195 public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) { 196 for (Map.Entry<BluetoothLeBroadcastAssistant.Callback, Executor> 197 callbackExecutorEntry : mCallbackExecutorMap.entrySet()) { 198 BluetoothLeBroadcastAssistant.Callback callback = 199 callbackExecutorEntry.getKey(); 200 Executor executor = callbackExecutorEntry.getValue(); 201 executor.execute( 202 () -> callback.onSourceRemoveFailed(sink, sourceId, reason)); 203 } 204 } 205 206 @Override 207 public void onReceiveStateChanged( 208 BluetoothDevice sink, 209 int sourceId, 210 BluetoothLeBroadcastReceiveState state) { 211 for (Map.Entry<BluetoothLeBroadcastAssistant.Callback, Executor> 212 callbackExecutorEntry : mCallbackExecutorMap.entrySet()) { 213 BluetoothLeBroadcastAssistant.Callback callback = 214 callbackExecutorEntry.getKey(); 215 Executor executor = callbackExecutorEntry.getValue(); 216 executor.execute( 217 () -> callback.onReceiveStateChanged(sink, sourceId, state)); 218 } 219 } 220 221 @Override 222 public void onSourceLost(int broadcastId) { 223 for (Map.Entry<BluetoothLeBroadcastAssistant.Callback, Executor> 224 callbackExecutorEntry : mCallbackExecutorMap.entrySet()) { 225 BluetoothLeBroadcastAssistant.Callback callback = 226 callbackExecutorEntry.getKey(); 227 Executor executor = callbackExecutorEntry.getValue(); 228 executor.execute(() -> callback.onSourceLost(broadcastId)); 229 } 230 } 231 }; 232 233 /** 234 * This class provides a set of callbacks that are invoked when scanning for Broadcast Sources 235 * is offloaded to a Broadcast Assistant. 236 * 237 * @hide 238 */ 239 @SystemApi 240 public interface Callback { 241 /** @hide */ 242 @Retention(RetentionPolicy.SOURCE) 243 @IntDef( 244 value = { 245 BluetoothStatusCodes.ERROR_UNKNOWN, 246 BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST, 247 BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST, 248 BluetoothStatusCodes.REASON_REMOTE_REQUEST, 249 BluetoothStatusCodes.REASON_SYSTEM_POLICY, 250 BluetoothStatusCodes.ERROR_HARDWARE_GENERIC, 251 BluetoothStatusCodes.ERROR_LE_BROADCAST_ASSISTANT_DUPLICATE_ADDITION, 252 BluetoothStatusCodes.ERROR_BAD_PARAMETERS, 253 BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR, 254 BluetoothStatusCodes.ERROR_REMOTE_NOT_ENOUGH_RESOURCES, 255 BluetoothStatusCodes.ERROR_LE_BROADCAST_ASSISTANT_INVALID_SOURCE_ID, 256 BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE, 257 BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED, 258 }) 259 @interface Reason {} 260 261 /** 262 * Callback invoked when the implementation started searching for nearby Broadcast Sources. 263 * 264 * @param reason reason code on why search has started 265 * @hide 266 */ 267 @SystemApi onSearchStarted(@eason int reason)268 void onSearchStarted(@Reason int reason); 269 270 /** 271 * Callback invoked when the implementation failed to start searching for nearby broadcast 272 * sources. 273 * 274 * @param reason reason for why search failed to start 275 * @hide 276 */ 277 @SystemApi onSearchStartFailed(@eason int reason)278 void onSearchStartFailed(@Reason int reason); 279 280 /** 281 * Callback invoked when the implementation stopped searching for nearby Broadcast Sources. 282 * 283 * @param reason reason code on why search has stopped 284 * @hide 285 */ 286 @SystemApi onSearchStopped(@eason int reason)287 void onSearchStopped(@Reason int reason); 288 289 /** 290 * Callback invoked when the implementation failed to stop searching for nearby broadcast 291 * sources. 292 * 293 * @param reason for why search failed to start 294 * @hide 295 */ 296 @SystemApi onSearchStopFailed(@eason int reason)297 void onSearchStopFailed(@Reason int reason); 298 299 /** 300 * Callback invoked when a new Broadcast Source is found together with the {@link 301 * BluetoothLeBroadcastMetadata}. 302 * 303 * @param source {@link BluetoothLeBroadcastMetadata} representing a Broadcast Source 304 * @hide 305 */ 306 @SystemApi onSourceFound(@onNull BluetoothLeBroadcastMetadata source)307 void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source); 308 309 /** 310 * Callback invoked when a new Broadcast Source has been successfully added to the Broadcast 311 * Sink. 312 * 313 * <p>Broadcast audio stream may not have been started after this callback, the caller need 314 * to monitor {@link #onReceiveStateChanged(BluetoothDevice, int, 315 * BluetoothLeBroadcastReceiveState)} to see if synchronization with Broadcast Source is 316 * successful 317 * 318 * <p>When <var>isGroupOp</var> is true when {@link #addSource(BluetoothDevice, 319 * BluetoothLeBroadcastMetadata, boolean)} is called, each Broadcast Sink device in the 320 * coordinated set will trigger and individual update 321 * 322 * <p>A new source could be added by the Broadcast Sink itself or other Broadcast Assistants 323 * connected to the Broadcast Sink and in this case the reason code will be {@link 324 * BluetoothStatusCodes#REASON_REMOTE_REQUEST} 325 * 326 * @param sink Broadcast Sink device on which a new Broadcast Source has been added 327 * @param sourceId source ID as defined in the BASS specification 328 * @param reason reason of source addition 329 * @hide 330 */ 331 @SystemApi onSourceAdded(@onNull BluetoothDevice sink, @Reason int sourceId, @Reason int reason)332 void onSourceAdded(@NonNull BluetoothDevice sink, @Reason int sourceId, @Reason int reason); 333 334 /** 335 * Callback invoked when the new Broadcast Source failed to be added to the Broadcast Sink. 336 * 337 * @param sink Broadcast Sink device on which a new Broadcast Source has been added 338 * @param source metadata representation of the Broadcast Source 339 * @param reason reason why the addition has failed 340 * @hide 341 */ 342 @SystemApi onSourceAddFailed( @onNull BluetoothDevice sink, @NonNull BluetoothLeBroadcastMetadata source, @Reason int reason)343 void onSourceAddFailed( 344 @NonNull BluetoothDevice sink, 345 @NonNull BluetoothLeBroadcastMetadata source, 346 @Reason int reason); 347 348 /** 349 * Callback invoked when an existing Broadcast Source within a Broadcast Sink has been 350 * modified. 351 * 352 * <p>Actual state after the modification will be delivered via the next {@link 353 * Callback#onReceiveStateChanged(BluetoothDevice, int, BluetoothLeBroadcastReceiveState)} 354 * callback. 355 * 356 * <p>A source could be modified by the Broadcast Sink itself or other Broadcast Assistants 357 * connected to the Broadcast Sink and in this case the reason code will be {@link 358 * BluetoothStatusCodes#REASON_REMOTE_REQUEST} 359 * 360 * @param sink Broadcast Sink device on which a Broadcast Source has been modified 361 * @param sourceId source ID as defined in the BASS specification 362 * @param reason reason of source modification 363 * @hide 364 */ 365 @SystemApi onSourceModified(@onNull BluetoothDevice sink, int sourceId, @Reason int reason)366 void onSourceModified(@NonNull BluetoothDevice sink, int sourceId, @Reason int reason); 367 368 /** 369 * Callback invoked when the Broadcast Assistant failed to modify an existing Broadcast 370 * Source on a Broadcast Sink. 371 * 372 * @param sink Broadcast Sink device on which a Broadcast Source has been modified 373 * @param sourceId source ID as defined in the BASS specification 374 * @param reason reason why the modification has failed 375 * @hide 376 */ 377 @SystemApi onSourceModifyFailed(@onNull BluetoothDevice sink, int sourceId, @Reason int reason)378 void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId, @Reason int reason); 379 380 /** 381 * Callback invoked when a Broadcast Source has been successfully removed from the Broadcast 382 * Sink. 383 * 384 * <p>No more update for the source ID via {@link 385 * Callback#onReceiveStateChanged(BluetoothDevice, int, BluetoothLeBroadcastReceiveState)} 386 * after this callback. 387 * 388 * <p>A source could be removed by the Broadcast Sink itself or other Broadcast Assistants 389 * connected to the Broadcast Sink and in this case the reason code will be {@link 390 * BluetoothStatusCodes#REASON_REMOTE_REQUEST} 391 * 392 * @param sink Broadcast Sink device from which a Broadcast Source has been removed 393 * @param sourceId source ID as defined in the BASS specification 394 * @param reason reason why the Broadcast Source was removed 395 * @hide 396 */ 397 @SystemApi onSourceRemoved(@onNull BluetoothDevice sink, int sourceId, @Reason int reason)398 void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId, @Reason int reason); 399 400 /** 401 * Callback invoked when the Broadcast Assistant failed to remove an existing Broadcast 402 * Source on a Broadcast Sink. 403 * 404 * @param sink Broadcast Sink device on which a Broadcast Source was to be removed 405 * @param sourceId source ID as defined in the BASS specification 406 * @param reason reason why the modification has failed 407 * @hide 408 */ 409 @SystemApi onSourceRemoveFailed(@onNull BluetoothDevice sink, int sourceId, @Reason int reason)410 void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId, @Reason int reason); 411 412 /** 413 * Callback invoked when the Broadcast Receive State information of a Broadcast Sink device 414 * changes. 415 * 416 * @param sink BASS server device that is also a Broadcast Sink device 417 * @param sourceId source ID as defined in the BASS specification 418 * @param state latest state information between the Broadcast Sink and a Broadcast Source 419 * @hide 420 */ 421 @SystemApi onReceiveStateChanged( @onNull BluetoothDevice sink, int sourceId, @NonNull BluetoothLeBroadcastReceiveState state)422 void onReceiveStateChanged( 423 @NonNull BluetoothDevice sink, 424 int sourceId, 425 @NonNull BluetoothLeBroadcastReceiveState state); 426 427 /** 428 * Callback invoked when the Broadcast Source is lost together with source broadcast id. 429 * 430 * <p>This callback is to notify source lost due to periodic advertising sync lost. Callback 431 * client can know that the source notified by {@link 432 * Callback#onSourceFound(BluetoothLeBroadcastMetadata)} before is not available any more 433 * after this callback. 434 * 435 * @param broadcastId broadcast ID as defined in the BASS specification 436 * @hide 437 */ 438 @FlaggedApi(Flags.FLAG_LEAUDIO_BROADCAST_MONITOR_SOURCE_SYNC_STATUS) 439 @SystemApi onSourceLost(int broadcastId)440 default void onSourceLost(int broadcastId) {} 441 } 442 443 /** 444 * Intent used to broadcast the change in connection state of devices via Broadcast Audio Scan 445 * Service (BASS). Please note that in a coordinated set, each set member will connect via BASS 446 * individually. Group operations on a single set member will propagate to the entire set. 447 * 448 * <p>For example, in the binaural case, there will be two different LE devices for the left and 449 * right side and each device will have their own connection state changes. If both devices 450 * belongs to on Coordinated Set, group operation on one of them will affect both devices. 451 * 452 * <p>This intent will have 3 extras: 453 * 454 * <ul> 455 * <li>{@link #EXTRA_STATE} - The current state of the profile. 456 * <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. 457 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. 458 * </ul> 459 * 460 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link 461 * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link 462 * #STATE_DISCONNECTING}. 463 * 464 * @hide 465 */ 466 @SystemApi 467 @RequiresBluetoothConnectPermission 468 @RequiresPermission( 469 allOf = { 470 android.Manifest.permission.BLUETOOTH_CONNECT, 471 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 472 }) 473 @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) 474 public static final String ACTION_CONNECTION_STATE_CHANGED = 475 "android.bluetooth.action.CONNECTION_STATE_CHANGED"; 476 477 private CloseGuard mCloseGuard; 478 private BluetoothAdapter mBluetoothAdapter; 479 480 private IBluetoothLeBroadcastAssistant mService; 481 482 /** 483 * Create a new instance of an LE Audio Broadcast Assistant. 484 * 485 * @hide 486 */ BluetoothLeBroadcastAssistant( @onNull Context context, @NonNull BluetoothAdapter bluetoothAdapter)487 /*package*/ BluetoothLeBroadcastAssistant( 488 @NonNull Context context, @NonNull BluetoothAdapter bluetoothAdapter) { 489 mBluetoothAdapter = bluetoothAdapter; 490 mService = null; 491 mCloseGuard = new CloseGuard(); 492 mCloseGuard.open("close"); 493 } 494 495 /** @hide */ 496 @SuppressWarnings("Finalize") // TODO(b/314811467) finalize()497 protected void finalize() { 498 if (mCloseGuard != null) { 499 mCloseGuard.warnIfOpen(); 500 } 501 close(); 502 } 503 504 /** @hide */ 505 @Override close()506 public void close() { 507 mBluetoothAdapter.closeProfileProxy(this); 508 } 509 510 /** @hide */ 511 @Override onServiceConnected(IBinder service)512 public void onServiceConnected(IBinder service) { 513 mService = IBluetoothLeBroadcastAssistant.Stub.asInterface(service); 514 // re-register the service-to-app callback 515 log("onServiceConnected"); 516 synchronized (mCallbackExecutorMap) { 517 if (mCallbackExecutorMap.isEmpty()) { 518 return; 519 } 520 try { 521 mService.registerCallback(mCallback); 522 } catch (RemoteException e) { 523 Log.e( 524 TAG, 525 "onServiceConnected: Failed to register " 526 + "Le Broadcaster Assistant callback", 527 e); 528 } 529 } 530 } 531 532 /** @hide */ 533 @Override onServiceDisconnected()534 public void onServiceDisconnected() { 535 mService = null; 536 } 537 getService()538 private IBluetoothLeBroadcastAssistant getService() { 539 return mService; 540 } 541 542 /** @hide */ 543 @Override getAdapter()544 public BluetoothAdapter getAdapter() { 545 return mBluetoothAdapter; 546 } 547 548 /** 549 * {@inheritDoc} 550 * 551 * @hide 552 */ 553 @SystemApi 554 @RequiresBluetoothConnectPermission 555 @RequiresPermission( 556 allOf = { 557 android.Manifest.permission.BLUETOOTH_CONNECT, 558 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 559 }) 560 @Override 561 @BluetoothProfile.BtProfileState getConnectionState(@onNull BluetoothDevice sink)562 public int getConnectionState(@NonNull BluetoothDevice sink) { 563 log("getConnectionState(" + sink + ")"); 564 Objects.requireNonNull(sink, "sink cannot be null"); 565 final IBluetoothLeBroadcastAssistant service = getService(); 566 final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; 567 if (service == null) { 568 Log.w(TAG, "Proxy not attached to service"); 569 if (DBG) log(Log.getStackTraceString(new Throwable())); 570 } else if (mBluetoothAdapter.isEnabled() && isValidDevice(sink)) { 571 try { 572 return service.getConnectionState(sink); 573 } catch (RemoteException e) { 574 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 575 } 576 } 577 return defaultValue; 578 } 579 580 /** 581 * {@inheritDoc} 582 * 583 * @hide 584 */ 585 @SystemApi 586 @RequiresBluetoothConnectPermission 587 @RequiresPermission( 588 allOf = { 589 android.Manifest.permission.BLUETOOTH_CONNECT, 590 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 591 }) 592 @Override 593 @NonNull getDevicesMatchingConnectionStates(@onNull int[] states)594 public List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) { 595 log("getDevicesMatchingConnectionStates()"); 596 Objects.requireNonNull(states, "states cannot be null"); 597 final IBluetoothLeBroadcastAssistant service = getService(); 598 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); 599 if (service == null) { 600 Log.w(TAG, "Proxy not attached to service"); 601 if (DBG) log(Log.getStackTraceString(new Throwable())); 602 } else if (mBluetoothAdapter.isEnabled()) { 603 try { 604 return service.getDevicesMatchingConnectionStates(states); 605 } catch (RemoteException e) { 606 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 607 } 608 } 609 return defaultValue; 610 } 611 612 /** 613 * {@inheritDoc} 614 * 615 * @hide 616 */ 617 @SystemApi 618 @RequiresBluetoothConnectPermission 619 @RequiresPermission( 620 allOf = { 621 android.Manifest.permission.BLUETOOTH_CONNECT, 622 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 623 }) 624 @Override getConnectedDevices()625 public @NonNull List<BluetoothDevice> getConnectedDevices() { 626 log("getConnectedDevices()"); 627 final IBluetoothLeBroadcastAssistant service = getService(); 628 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); 629 if (service == null) { 630 Log.w(TAG, "Proxy not attached to service"); 631 if (DBG) log(Log.getStackTraceString(new Throwable())); 632 } else if (mBluetoothAdapter.isEnabled()) { 633 try { 634 return service.getConnectedDevices(); 635 } catch (RemoteException e) { 636 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 637 } 638 } 639 return defaultValue; 640 } 641 642 /** 643 * Set connection policy of the profile. 644 * 645 * <p>The device should already be paired. Connection policy can be one of {@link 646 * #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, {@link 647 * #CONNECTION_POLICY_UNKNOWN} 648 * 649 * @param device Paired bluetooth device 650 * @param connectionPolicy is the connection policy to set to for this profile 651 * @return true if connectionPolicy is set, false on error 652 * @throws NullPointerException if <var>device</var> is null 653 * @hide 654 */ 655 @SystemApi 656 @RequiresBluetoothConnectPermission 657 @RequiresPermission( 658 allOf = { 659 android.Manifest.permission.BLUETOOTH_CONNECT, 660 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 661 }) setConnectionPolicy( @onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)662 public boolean setConnectionPolicy( 663 @NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { 664 log("setConnectionPolicy()"); 665 Objects.requireNonNull(device, "device cannot be null"); 666 final IBluetoothLeBroadcastAssistant service = getService(); 667 final boolean defaultValue = false; 668 if (service == null) { 669 Log.w(TAG, "Proxy not attached to service"); 670 if (DBG) log(Log.getStackTraceString(new Throwable())); 671 } else if (mBluetoothAdapter.isEnabled() 672 && isValidDevice(device) 673 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 674 || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { 675 try { 676 return service.setConnectionPolicy(device, connectionPolicy); 677 } catch (RemoteException e) { 678 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 679 } 680 } 681 return defaultValue; 682 } 683 684 /** 685 * Get the connection policy of the profile. 686 * 687 * <p>The connection policy can be any of: {@link #CONNECTION_POLICY_ALLOWED}, {@link 688 * #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 689 * 690 * @param device Bluetooth device 691 * @return connection policy of the device 692 * @throws NullPointerException if <var>device</var> is null 693 * @hide 694 */ 695 @SystemApi 696 @RequiresBluetoothConnectPermission 697 @RequiresPermission( 698 allOf = { 699 android.Manifest.permission.BLUETOOTH_CONNECT, 700 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 701 }) getConnectionPolicy(@onNull BluetoothDevice device)702 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { 703 log("getConnectionPolicy()"); 704 Objects.requireNonNull(device, "device cannot be null"); 705 final IBluetoothLeBroadcastAssistant service = getService(); 706 final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 707 if (service == null) { 708 Log.w(TAG, "Proxy not attached to service"); 709 if (DBG) log(Log.getStackTraceString(new Throwable())); 710 } else if (mBluetoothAdapter.isEnabled() && isValidDevice(device)) { 711 try { 712 return service.getConnectionPolicy(device); 713 } catch (RemoteException e) { 714 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 715 } 716 } 717 return defaultValue; 718 } 719 720 /** 721 * Register a {@link Callback} that will be invoked during the operation of this profile. 722 * 723 * <p>Repeated registration of the same <var>callback</var> object after the first call to this 724 * method will result with IllegalArgumentException being thrown, even when the 725 * <var>executor</var> is different. API caller would have to call {@link 726 * #unregisterCallback(Callback)} with the same callback object before registering it again. 727 * 728 * @param executor an {@link Executor} to execute given callback 729 * @param callback user implementation of the {@link Callback} 730 * @throws NullPointerException if a null executor, or callback is given 731 * @throws IllegalArgumentException if the same <var>callback<var> is already registered 732 * @hide 733 */ 734 @SystemApi 735 @RequiresBluetoothConnectPermission 736 @RequiresPermission( 737 allOf = { 738 android.Manifest.permission.BLUETOOTH_CONNECT, 739 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 740 }) registerCallback( @onNull @allbackExecutor Executor executor, @NonNull Callback callback)741 public void registerCallback( 742 @NonNull @CallbackExecutor Executor executor, @NonNull Callback callback) { 743 Objects.requireNonNull(executor, "executor cannot be null"); 744 Objects.requireNonNull(callback, "callback cannot be null"); 745 log("registerCallback"); 746 747 synchronized (mCallbackExecutorMap) { 748 // If the callback map is empty, we register the service-to-app callback 749 if (mCallbackExecutorMap.isEmpty()) { 750 if (!mBluetoothAdapter.isEnabled()) { 751 /* If Bluetooth is off, just store callback and it will be registered 752 * when Bluetooth is on 753 */ 754 mCallbackExecutorMap.put(callback, executor); 755 return; 756 } 757 try { 758 final IBluetoothLeBroadcastAssistant service = getService(); 759 if (service != null) { 760 service.registerCallback(mCallback); 761 } 762 } catch (RemoteException e) { 763 throw e.rethrowAsRuntimeException(); 764 } 765 } 766 767 // Adds the passed in callback to our map of callbacks to executors 768 if (mCallbackExecutorMap.containsKey(callback)) { 769 throw new IllegalArgumentException("This callback has already been registered"); 770 } 771 mCallbackExecutorMap.put(callback, executor); 772 } 773 } 774 775 /** 776 * Unregister the specified {@link Callback}. 777 * 778 * <p>The same {@link Callback} object used when calling {@link #registerCallback(Executor, 779 * Callback)} must be used. 780 * 781 * <p>Callbacks are automatically unregistered when application process goes away. 782 * 783 * @param callback user implementation of the {@link Callback} 784 * @throws NullPointerException when callback is null 785 * @throws IllegalArgumentException when the <var>callback</var> was not registered before 786 * @hide 787 */ 788 @SystemApi 789 @RequiresBluetoothConnectPermission 790 @RequiresPermission( 791 allOf = { 792 android.Manifest.permission.BLUETOOTH_CONNECT, 793 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 794 }) unregisterCallback(@onNull Callback callback)795 public void unregisterCallback(@NonNull Callback callback) { 796 Objects.requireNonNull(callback, "callback cannot be null"); 797 log("unregisterCallback"); 798 799 synchronized (mCallbackExecutorMap) { 800 if (mCallbackExecutorMap.remove(callback) == null) { 801 throw new IllegalArgumentException("This callback has not been registered"); 802 } 803 804 // If the callback map is empty, we unregister the service-to-app callback 805 if (mCallbackExecutorMap.isEmpty()) { 806 try { 807 final IBluetoothLeBroadcastAssistant service = getService(); 808 if (service != null) { 809 service.unregisterCallback(mCallback); 810 } 811 } catch (RemoteException e) { 812 throw e.rethrowAsRuntimeException(); 813 } 814 } 815 } 816 } 817 818 /** 819 * Search for LE Audio Broadcast Sources on behalf of all devices connected via Broadcast Audio 820 * Scan Service, filtered by <var>filters</var>. 821 * 822 * <p>On success, {@link Callback#onSearchStarted(int)} will be called with reason code {@link 823 * BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. 824 * 825 * <p>On failure, {@link Callback#onSearchStartFailed(int)} will be called with reason code 826 * 827 * <p>The implementation will also synchronize with discovered Broadcast Sources and get their 828 * metadata before passing the Broadcast Source metadata back to the application using {@link 829 * Callback#onSourceFound(BluetoothLeBroadcastMetadata)}. 830 * 831 * <p>Please disconnect the Broadcast Sink's BASS server by calling {@link 832 * #setConnectionPolicy(BluetoothDevice, int)} with {@link 833 * BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} if you do not want the Broadcast Sink to 834 * receive notifications about this search before calling this method. 835 * 836 * <p>App must also have {@link android.Manifest.permission#ACCESS_FINE_LOCATION 837 * ACCESS_FINE_LOCATION} permission in order to get results. 838 * 839 * <p><var>filters</var> will be AND'ed with internal filters in the implementation and {@link 840 * ScanSettings} will be managed by the implementation. 841 * 842 * @param filters {@link ScanFilter}s for finding exact Broadcast Source, if no filter is 843 * needed, please provide an empty list instead 844 * @throws NullPointerException when <var>filters</var> argument is null 845 * @throws IllegalStateException when no callback is registered 846 * @hide 847 */ 848 @SystemApi 849 @RequiresBluetoothScanPermission 850 @RequiresBluetoothLocationPermission 851 @RequiresPermission( 852 allOf = { 853 android.Manifest.permission.BLUETOOTH_SCAN, 854 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 855 }) startSearchingForSources(@onNull List<ScanFilter> filters)856 public void startSearchingForSources(@NonNull List<ScanFilter> filters) { 857 log("searchForBroadcastSources"); 858 Objects.requireNonNull(filters, "filters can be empty, but not null"); 859 if (mCallback == null) { 860 throw new IllegalStateException("No callback was ever registered"); 861 } 862 863 synchronized (mCallbackExecutorMap) { 864 if (mCallbackExecutorMap.isEmpty()) { 865 throw new IllegalStateException("All callbacks are unregistered"); 866 } 867 } 868 869 final IBluetoothLeBroadcastAssistant service = getService(); 870 if (service == null) { 871 Log.w(TAG, "Proxy not attached to service"); 872 if (DBG) log(Log.getStackTraceString(new Throwable())); 873 } else if (mBluetoothAdapter.isEnabled()) { 874 try { 875 service.startSearchingForSources(filters); 876 } catch (RemoteException e) { 877 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 878 } 879 } 880 } 881 882 /** 883 * Stops an ongoing search for nearby Broadcast Sources. 884 * 885 * <p>On success, {@link Callback#onSearchStopped(int)} will be called with reason code {@link 886 * BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. On failure, {@link 887 * Callback#onSearchStopFailed(int)} will be called with reason code 888 * 889 * @throws IllegalStateException if callback was not registered 890 * @hide 891 */ 892 @SystemApi 893 @RequiresBluetoothConnectPermission 894 @RequiresPermission( 895 allOf = { 896 android.Manifest.permission.BLUETOOTH_CONNECT, 897 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 898 }) stopSearchingForSources()899 public void stopSearchingForSources() { 900 log("stopSearchingForSources:"); 901 if (mCallback == null) { 902 throw new IllegalStateException("No callback was ever registered"); 903 } 904 905 synchronized (mCallbackExecutorMap) { 906 if (mCallbackExecutorMap.isEmpty()) { 907 throw new IllegalStateException("All callbacks are unregistered"); 908 } 909 } 910 911 final IBluetoothLeBroadcastAssistant service = getService(); 912 if (service == null) { 913 Log.w(TAG, "Proxy not attached to service"); 914 if (DBG) log(Log.getStackTraceString(new Throwable())); 915 } else if (mBluetoothAdapter.isEnabled()) { 916 try { 917 service.stopSearchingForSources(); 918 } catch (RemoteException e) { 919 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 920 } 921 } 922 } 923 924 /** 925 * Return true if a search has been started by this application. 926 * 927 * @return true if a search has been started by this application 928 * @hide 929 */ 930 @SystemApi 931 @RequiresBluetoothConnectPermission 932 @RequiresPermission( 933 allOf = { 934 android.Manifest.permission.BLUETOOTH_CONNECT, 935 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 936 }) isSearchInProgress()937 public boolean isSearchInProgress() { 938 log("stopSearchingForSources:"); 939 final IBluetoothLeBroadcastAssistant service = getService(); 940 final boolean defaultValue = false; 941 if (service == null) { 942 Log.w(TAG, "Proxy not attached to service"); 943 if (DBG) log(Log.getStackTraceString(new Throwable())); 944 } else if (mBluetoothAdapter.isEnabled()) { 945 try { 946 return service.isSearchInProgress(); 947 } catch (RemoteException e) { 948 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 949 } 950 } 951 return defaultValue; 952 } 953 954 /** 955 * Add a Broadcast Source to the Broadcast Sink. 956 * 957 * <p>Caller can modify <var>sourceMetadata</var> before using it in this method to set a 958 * Broadcast Code, to select a different Broadcast Channel in a Broadcast Source such as channel 959 * with a different language, and so on. What can be modified is listed in the documentation of 960 * {@link #modifySource(BluetoothDevice, int, BluetoothLeBroadcastMetadata)} and can also be 961 * modified after a source is added. 962 * 963 * <p>On success, {@link Callback#onSourceAdded(BluetoothDevice, int, int)} will be invoked with 964 * a <var>sourceID</var> assigned by the Broadcast Sink with reason code {@link 965 * BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. However, this callback only indicates that 966 * the Broadcast Sink has allocated resource to receive audio from the Broadcast Source, and 967 * audio stream may not have started. The caller should then wait for {@link 968 * Callback#onReceiveStateChanged(BluetoothDevice, int, BluetoothLeBroadcastReceiveState)} 969 * callback to monitor the encryption and audio sync state. 970 * 971 * <p>Note that wrong broadcast code will not prevent the source from being added to the 972 * Broadcast Sink. Caller should modify the current source to correct the broadcast code. 973 * 974 * <p>On failure, {@link Callback#onSourceAddFailed(BluetoothDevice, 975 * BluetoothLeBroadcastMetadata, int)} will be invoked with the same <var>source</var> metadata 976 * and reason code 977 * 978 * <p>When too many sources was added to Broadcast sink, error {@link 979 * BluetoothStatusCodes#ERROR_REMOTE_NOT_ENOUGH_RESOURCES} will be delivered. In this case, 980 * check the capacity of Broadcast sink via {@link #getMaximumSourceCapacity(BluetoothDevice)} 981 * and the current list of sources via {@link #getAllSources(BluetoothDevice)}. 982 * 983 * <p>Some sources might be added by other Broadcast Assistants and hence was not in {@link 984 * Callback#onSourceAdded(BluetoothDevice, int, int)} callback, but will be updated via {@link 985 * Callback#onReceiveStateChanged(BluetoothDevice, int, BluetoothLeBroadcastReceiveState)} 986 * 987 * <p>If there are multiple members in the coordinated set the sink belongs to, and isGroupOp is 988 * set to true, the Broadcast Source will be added to each sink in the coordinated set and a 989 * separate {@link Callback#onSourceAdded} callback will be invoked for each member of the 990 * coordinated set. 991 * 992 * <p>The <var>isGroupOp</var> option is sticky. This means that subsequent operations using 993 * {@link #modifySource(BluetoothDevice, int, BluetoothLeBroadcastMetadata)} and {@link 994 * #removeSource(BluetoothDevice, int)} will act on all devices in the same coordinated set for 995 * the <var>sink</var> and <var>sourceID</var> pair until the <var>sourceId</var> is removed 996 * from the <var>sink</var> by any Broadcast role (could be another remote device). 997 * 998 * <p>When <var>isGroupOp</var> is true, if one Broadcast Sink in a coordinated set disconnects 999 * from this Broadcast Assistant or lost the Broadcast Source, this Broadcast Assistant will try 1000 * to add it back automatically to make sure the whole coordinated set is in the same state. 1001 * 1002 * @param sink Broadcast Sink to which the Broadcast Source should be added 1003 * @param sourceMetadata Broadcast Source metadata to be added to the Broadcast Sink 1004 * @param isGroupOp {@code true} if Application wants to perform this operation for all 1005 * coordinated set members throughout this session. Otherwise, caller would have to add, 1006 * modify, and remove individual set members. 1007 * @throws NullPointerException if <var>sink</var> or <var>source</var> is null 1008 * @throws IllegalStateException if callback was not registered 1009 * @hide 1010 */ 1011 @SystemApi 1012 @RequiresBluetoothConnectPermission 1013 @RequiresPermission( 1014 allOf = { 1015 android.Manifest.permission.BLUETOOTH_CONNECT, 1016 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 1017 }) addSource( @onNull BluetoothDevice sink, @NonNull BluetoothLeBroadcastMetadata sourceMetadata, boolean isGroupOp)1018 public void addSource( 1019 @NonNull BluetoothDevice sink, 1020 @NonNull BluetoothLeBroadcastMetadata sourceMetadata, 1021 boolean isGroupOp) { 1022 log("addBroadcastSource: " + sourceMetadata + " on " + sink); 1023 Objects.requireNonNull(sink, "sink cannot be null"); 1024 Objects.requireNonNull(sourceMetadata, "sourceMetadata cannot be null"); 1025 if (mCallback == null) { 1026 throw new IllegalStateException("No callback was ever registered"); 1027 } 1028 1029 synchronized (mCallbackExecutorMap) { 1030 if (mCallbackExecutorMap.isEmpty()) { 1031 throw new IllegalStateException("All callbacks are unregistered"); 1032 } 1033 } 1034 1035 final IBluetoothLeBroadcastAssistant service = getService(); 1036 if (service == null) { 1037 Log.w(TAG, "Proxy not attached to service"); 1038 if (DBG) log(Log.getStackTraceString(new Throwable())); 1039 } else if (mBluetoothAdapter.isEnabled() && isValidDevice(sink)) { 1040 try { 1041 service.addSource(sink, sourceMetadata, isGroupOp); 1042 } catch (RemoteException e) { 1043 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1044 } 1045 } 1046 } 1047 1048 /** 1049 * Modify the Broadcast Source information on a Broadcast Sink. 1050 * 1051 * <p>One can modify {@link BluetoothLeBroadcastMetadata#getBroadcastCode()} if {@link 1052 * BluetoothLeBroadcastReceiveState#getBigEncryptionState()} returns {@link 1053 * BluetoothLeBroadcastReceiveState#BIG_ENCRYPTION_STATE_BAD_CODE} or {@link 1054 * BluetoothLeBroadcastReceiveState#BIG_ENCRYPTION_STATE_CODE_REQUIRED} 1055 * 1056 * <p>One can modify {@link BluetoothLeBroadcastMetadata#getPaSyncInterval()} if the Broadcast 1057 * Assistant received updated information. 1058 * 1059 * <p>One can modify {@link BluetoothLeBroadcastChannel#isSelected()} to select different 1060 * broadcast channel to listen to (one per {@link BluetoothLeBroadcastSubgroup} or set {@link 1061 * BluetoothLeBroadcastSubgroup#isNoChannelPreference()} to leave the choice to the Broadcast 1062 * Sink. 1063 * 1064 * <p>One can modify {@link BluetoothLeBroadcastSubgroup#getContentMetadata()} if the subgroup 1065 * metadata changes and the Broadcast Sink need help updating the metadata from Broadcast 1066 * Assistant. 1067 * 1068 * <p>Each of the above modifications can be accepted or rejected by the Broadcast Assistant 1069 * implement and/or the Broadcast Sink. 1070 * 1071 * <p>On success, {@link Callback#onSourceModified(BluetoothDevice, int, int)} will be invoked 1072 * with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. 1073 * 1074 * <p>On failure, {@link Callback#onSourceModifyFailed(BluetoothDevice, int, int)} will be 1075 * invoked with reason code. 1076 * 1077 * <p>If there are multiple members in the coordinated set the sink belongs to, and isGroupOp is 1078 * set to true during {@link #addSource(BluetoothDevice, BluetoothLeBroadcastMetadata, 1079 * boolean)}, the source will be modified on each sink in the coordinated set and a separate 1080 * {@link Callback#onSourceModified(BluetoothDevice, int, int)} callback will be invoked for 1081 * each member of the coordinated set. 1082 * 1083 * @param sink Broadcast Sink to which the Broadcast Source should be updated 1084 * @param sourceId source ID as delivered in {@link Callback#onSourceAdded(BluetoothDevice, int, 1085 * int)} 1086 * @param updatedMetadata updated Broadcast Source metadata to be updated on the Broadcast Sink 1087 * @throws IllegalStateException if callback was not registered 1088 * @throws NullPointerException if <var>sink</var> or <var>updatedMetadata</var> is null 1089 * @hide 1090 */ 1091 @SystemApi 1092 @RequiresBluetoothConnectPermission 1093 @RequiresPermission( 1094 allOf = { 1095 android.Manifest.permission.BLUETOOTH_CONNECT, 1096 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 1097 }) modifySource( @onNull BluetoothDevice sink, int sourceId, @NonNull BluetoothLeBroadcastMetadata updatedMetadata)1098 public void modifySource( 1099 @NonNull BluetoothDevice sink, 1100 int sourceId, 1101 @NonNull BluetoothLeBroadcastMetadata updatedMetadata) { 1102 log("updateBroadcastSource: " + updatedMetadata + " on " + sink); 1103 Objects.requireNonNull(sink, "sink cannot be null"); 1104 Objects.requireNonNull(updatedMetadata, "updatedMetadata cannot be null"); 1105 if (mCallback == null) { 1106 throw new IllegalStateException("No callback was ever registered"); 1107 } 1108 1109 synchronized (mCallbackExecutorMap) { 1110 if (mCallbackExecutorMap.isEmpty()) { 1111 throw new IllegalStateException("All callbacks are unregistered"); 1112 } 1113 } 1114 1115 final IBluetoothLeBroadcastAssistant service = getService(); 1116 if (service == null) { 1117 Log.w(TAG, "Proxy not attached to service"); 1118 if (DBG) log(Log.getStackTraceString(new Throwable())); 1119 } else if (mBluetoothAdapter.isEnabled() && isValidDevice(sink)) { 1120 try { 1121 service.modifySource(sink, sourceId, updatedMetadata); 1122 } catch (RemoteException e) { 1123 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1124 } 1125 } 1126 } 1127 1128 /** 1129 * Removes the Broadcast Source from a Broadcast Sink. 1130 * 1131 * <p>On success, {@link Callback#onSourceRemoved(BluetoothDevice, int, int)} will be invoked 1132 * with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. 1133 * 1134 * <p>On failure, {@link Callback#onSourceRemoveFailed(BluetoothDevice, int, int)} will be 1135 * invoked with reason code. 1136 * 1137 * <p>If there are multiple members in the coordinated set the sink belongs to, and isGroupOp is 1138 * set to true during {@link #addSource(BluetoothDevice, BluetoothLeBroadcastMetadata, 1139 * boolean)}, the source will be removed from each sink in the coordinated set and a separate 1140 * {@link Callback#onSourceRemoved(BluetoothDevice, int, int)} callback will be invoked for each 1141 * member of the coordinated set. 1142 * 1143 * @param sink Broadcast Sink from which a Broadcast Source should be removed 1144 * @param sourceId source ID as delivered in {@link Callback#onSourceAdded(BluetoothDevice, int, 1145 * int)} 1146 * @throws NullPointerException when the <var>sink</var> is null 1147 * @throws IllegalStateException if callback was not registered 1148 * @hide 1149 */ 1150 @SystemApi 1151 @RequiresBluetoothConnectPermission 1152 @RequiresPermission( 1153 allOf = { 1154 android.Manifest.permission.BLUETOOTH_CONNECT, 1155 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 1156 }) removeSource(@onNull BluetoothDevice sink, int sourceId)1157 public void removeSource(@NonNull BluetoothDevice sink, int sourceId) { 1158 log("removeBroadcastSource: " + sourceId + " from " + sink); 1159 Objects.requireNonNull(sink, "sink cannot be null"); 1160 if (mCallback == null) { 1161 throw new IllegalStateException("No callback was ever registered"); 1162 } 1163 1164 synchronized (mCallbackExecutorMap) { 1165 if (mCallbackExecutorMap.isEmpty()) { 1166 throw new IllegalStateException("All callbacks are unregistered"); 1167 } 1168 } 1169 1170 final IBluetoothLeBroadcastAssistant service = getService(); 1171 if (service == null) { 1172 Log.w(TAG, "Proxy not attached to service"); 1173 if (DBG) log(Log.getStackTraceString(new Throwable())); 1174 } else if (mBluetoothAdapter.isEnabled() && isValidDevice(sink)) { 1175 try { 1176 service.removeSource(sink, sourceId); 1177 } catch (RemoteException e) { 1178 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1179 } 1180 } 1181 } 1182 1183 /** 1184 * Get information about all Broadcast Sources that a Broadcast Sink knows about. 1185 * 1186 * @param sink Broadcast Sink from which to get all Broadcast Sources 1187 * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState} stored 1188 * in the Broadcast Sink 1189 * @throws NullPointerException when <var>sink</var> is null 1190 * @hide 1191 */ 1192 @SystemApi 1193 @RequiresBluetoothConnectPermission 1194 @RequiresPermission( 1195 allOf = { 1196 android.Manifest.permission.BLUETOOTH_CONNECT, 1197 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 1198 }) 1199 @NonNull getAllSources(@onNull BluetoothDevice sink)1200 public List<BluetoothLeBroadcastReceiveState> getAllSources(@NonNull BluetoothDevice sink) { 1201 log("getAllSources()"); 1202 Objects.requireNonNull(sink, "sink cannot be null"); 1203 final IBluetoothLeBroadcastAssistant service = getService(); 1204 final List<BluetoothLeBroadcastReceiveState> defaultValue = 1205 new ArrayList<BluetoothLeBroadcastReceiveState>(); 1206 if (service == null) { 1207 Log.w(TAG, "Proxy not attached to service"); 1208 if (DBG) log(Log.getStackTraceString(new Throwable())); 1209 } else if (mBluetoothAdapter.isEnabled()) { 1210 try { 1211 return service.getAllSources(sink); 1212 } catch (RemoteException e) { 1213 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1214 } 1215 } 1216 return defaultValue; 1217 } 1218 1219 /** 1220 * Get maximum number of sources that can be added to this Broadcast Sink. 1221 * 1222 * @param sink Broadcast Sink device 1223 * @return maximum number of sources that can be added to this Broadcast Sink 1224 * @throws NullPointerException when <var>sink</var> is null 1225 * @hide 1226 */ 1227 @SystemApi getMaximumSourceCapacity(@onNull BluetoothDevice sink)1228 public int getMaximumSourceCapacity(@NonNull BluetoothDevice sink) { 1229 Objects.requireNonNull(sink, "sink cannot be null"); 1230 final IBluetoothLeBroadcastAssistant service = getService(); 1231 final int defaultValue = 0; 1232 if (service == null) { 1233 Log.w(TAG, "Proxy not attached to service"); 1234 if (DBG) log(Log.getStackTraceString(new Throwable())); 1235 } else if (mBluetoothAdapter.isEnabled() && isValidDevice(sink)) { 1236 try { 1237 return service.getMaximumSourceCapacity(sink); 1238 } catch (RemoteException e) { 1239 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 1240 } 1241 } 1242 return defaultValue; 1243 } 1244 log(@onNull String msg)1245 private static void log(@NonNull String msg) { 1246 if (DBG) { 1247 Log.d(TAG, msg); 1248 } 1249 } 1250 isValidDevice(@ullable BluetoothDevice device)1251 private static boolean isValidDevice(@Nullable BluetoothDevice device) { 1252 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 1253 } 1254 } 1255