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