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