• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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