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