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