1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.bluetooth;
18 
19 import android.annotation.FlaggedApi;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SdkConstant;
25 import android.annotation.SdkConstant.SdkConstantType;
26 import android.annotation.SuppressLint;
27 import android.annotation.SystemApi;
28 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
29 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
30 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
31 import android.compat.annotation.UnsupportedAppUsage;
32 import android.content.AttributionSource;
33 import android.content.Context;
34 import android.os.Build;
35 import android.os.IBinder;
36 import android.os.ParcelUuid;
37 import android.os.RemoteException;
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.Collection;
45 import java.util.Collections;
46 import java.util.List;
47 
48 /**
49  * This class provides the public APIs to control the Bluetooth A2DP profile.
50  *
51  * <p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP Service via IPC. Use {@link
52  * BluetoothAdapter#getProfileProxy} to get the BluetoothA2dp proxy object.
53  *
54  * <p>Android only supports one connected Bluetooth A2dp device at a time. Each method is protected
55  * with its appropriate permission.
56  */
57 public final class BluetoothA2dp implements BluetoothProfile {
58     private static final String TAG = "BluetoothA2dp";
59     private static final boolean DBG = true;
60     private static final boolean VDBG = false;
61 
62     /**
63      * Intent used to broadcast the change in connection state of the A2DP profile.
64      *
65      * <p>This intent will have 3 extras:
66      *
67      * <ul>
68      *   <li>{@link #EXTRA_STATE} - The current state of the profile.
69      *   <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
70      *   <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
71      * </ul>
72      *
73      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
74      * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link
75      * #STATE_DISCONNECTING}.
76      */
77     @RequiresLegacyBluetoothPermission
78     @RequiresBluetoothConnectPermission
79     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
80     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
81     public static final String ACTION_CONNECTION_STATE_CHANGED =
82             "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
83 
84     /**
85      * Intent used to broadcast the change in the Playing state of the A2DP profile.
86      *
87      * <p>This intent will have 3 extras:
88      *
89      * <ul>
90      *   <li>{@link #EXTRA_STATE} - The current state of the profile.
91      *   <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
92      *   <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
93      * </ul>
94      *
95      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
96      * #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
97      */
98     @RequiresLegacyBluetoothPermission
99     @RequiresBluetoothConnectPermission
100     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
101     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
102     public static final String ACTION_PLAYING_STATE_CHANGED =
103             "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
104 
105     /** @hide */
106     @RequiresBluetoothConnectPermission
107     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
108     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
109     public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED =
110             "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED";
111 
112     /**
113      * Intent used to broadcast the selection of a connected device as active.
114      *
115      * <p>This intent will have one extra:
116      *
117      * <ul>
118      *   <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can be null if no device
119      *       is active.
120      * </ul>
121      *
122      * @hide
123      */
124     @SystemApi
125     @RequiresLegacyBluetoothPermission
126     @RequiresBluetoothConnectPermission
127     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
128     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
129     @SuppressLint("ActionValue")
130     public static final String ACTION_ACTIVE_DEVICE_CHANGED =
131             "android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED";
132 
133     /**
134      * Intent used to broadcast the change in the Audio Codec state of the A2DP Source profile.
135      *
136      * <p>This intent will have 2 extras:
137      *
138      * <ul>
139      *   <li>{@link BluetoothCodecStatus#EXTRA_CODEC_STATUS} - The codec status.
140      *   <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device if the device is currently
141      *       connected, otherwise it is not included.
142      * </ul>
143      *
144      * @hide
145      */
146     @SystemApi
147     @RequiresLegacyBluetoothPermission
148     @RequiresBluetoothConnectPermission
149     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
150     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
151     @SuppressLint("ActionValue")
152     public static final String ACTION_CODEC_CONFIG_CHANGED =
153             "android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED";
154 
155     /**
156      * A2DP sink device is streaming music. This state can be one of {@link #EXTRA_STATE} or {@link
157      * #EXTRA_PREVIOUS_STATE} of {@link #ACTION_PLAYING_STATE_CHANGED} intent.
158      */
159     public static final int STATE_PLAYING = 10;
160 
161     /**
162      * A2DP sink device is NOT streaming music. This state can be one of {@link #EXTRA_STATE} or
163      * {@link #EXTRA_PREVIOUS_STATE} of {@link #ACTION_PLAYING_STATE_CHANGED} intent.
164      */
165     public static final int STATE_NOT_PLAYING = 11;
166 
167     /** @hide */
168     @IntDef(
169             prefix = "OPTIONAL_CODECS_",
170             value = {
171                 OPTIONAL_CODECS_SUPPORT_UNKNOWN,
172                 OPTIONAL_CODECS_NOT_SUPPORTED,
173                 OPTIONAL_CODECS_SUPPORTED
174             })
175     @Retention(RetentionPolicy.SOURCE)
176     public @interface OptionalCodecsSupportStatus {}
177 
178     /**
179      * We don't have a stored preference for whether or not the given A2DP sink device supports
180      * optional codecs.
181      *
182      * @hide
183      */
184     @SystemApi public static final int OPTIONAL_CODECS_SUPPORT_UNKNOWN = -1;
185 
186     /**
187      * The given A2DP sink device does not support optional codecs.
188      *
189      * @hide
190      */
191     @SystemApi public static final int OPTIONAL_CODECS_NOT_SUPPORTED = 0;
192 
193     /**
194      * The given A2DP sink device does support optional codecs.
195      *
196      * @hide
197      */
198     @SystemApi public static final int OPTIONAL_CODECS_SUPPORTED = 1;
199 
200     /** @hide */
201     @IntDef(
202             prefix = "OPTIONAL_CODECS_PREF_",
203             value = {
204                 OPTIONAL_CODECS_PREF_UNKNOWN,
205                 OPTIONAL_CODECS_PREF_DISABLED,
206                 OPTIONAL_CODECS_PREF_ENABLED
207             })
208     @Retention(RetentionPolicy.SOURCE)
209     public @interface OptionalCodecsPreferenceStatus {}
210 
211     /**
212      * We don't have a stored preference for whether optional codecs should be enabled or disabled
213      * for the given A2DP device.
214      *
215      * @hide
216      */
217     @SystemApi public static final int OPTIONAL_CODECS_PREF_UNKNOWN = -1;
218 
219     /**
220      * Optional codecs should be disabled for the given A2DP device.
221      *
222      * @hide
223      */
224     @SystemApi public static final int OPTIONAL_CODECS_PREF_DISABLED = 0;
225 
226     /**
227      * Optional codecs should be enabled for the given A2DP device.
228      *
229      * @hide
230      */
231     @SystemApi public static final int OPTIONAL_CODECS_PREF_ENABLED = 1;
232 
233     /** @hide */
234     @IntDef(
235             prefix = "DYNAMIC_BUFFER_SUPPORT_",
236             value = {
237                 DYNAMIC_BUFFER_SUPPORT_NONE,
238                 DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD,
239                 DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING
240             })
241     @Retention(RetentionPolicy.SOURCE)
242     public @interface Type {}
243 
244     /**
245      * Indicates the supported type of Dynamic Audio Buffer is not supported.
246      *
247      * @hide
248      */
249     @SystemApi public static final int DYNAMIC_BUFFER_SUPPORT_NONE = 0;
250 
251     /**
252      * Indicates the supported type of Dynamic Audio Buffer is A2DP offload.
253      *
254      * @hide
255      */
256     @SystemApi public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD = 1;
257 
258     /**
259      * Indicates the supported type of Dynamic Audio Buffer is A2DP software encoding.
260      *
261      * @hide
262      */
263     @SystemApi public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING = 2;
264 
265     private final BluetoothAdapter mAdapter;
266     private final AttributionSource mAttributionSource;
267 
268     private IBluetoothA2dp mService;
269 
270     /**
271      * Create a BluetoothA2dp proxy object for interacting with the local Bluetooth A2DP service.
272      */
BluetoothA2dp(Context context, BluetoothAdapter adapter)273     /* package */ BluetoothA2dp(Context context, BluetoothAdapter adapter) {
274         mAdapter = adapter;
275         mAttributionSource = adapter.getAttributionSource();
276         mService = null;
277     }
278 
279     /** @hide */
280     @UnsupportedAppUsage
close()281     public void close() {
282         mAdapter.closeProfileProxy(this);
283     }
284 
285     /** @hide */
286     @Override
onServiceConnected(IBinder service)287     public void onServiceConnected(IBinder service) {
288         mService = IBluetoothA2dp.Stub.asInterface(service);
289     }
290 
291     /** @hide */
292     @Override
onServiceDisconnected()293     public void onServiceDisconnected() {
294         mService = null;
295     }
296 
getService()297     private IBluetoothA2dp getService() {
298         return mService;
299     }
300 
301     /** @hide */
302     @Override
getAdapter()303     public BluetoothAdapter getAdapter() {
304         return mAdapter;
305     }
306 
307     @Override
308     @SuppressWarnings("Finalize") // empty finalize for api signature
finalize()309     public void finalize() {
310         // The empty finalize needs to be kept or the
311         // cts signature tests would fail.
312     }
313 
314     /**
315      * Initiate connection to a profile of the remote Bluetooth device.
316      *
317      * <p>This API returns false in scenarios like the profile on the device is already connected or
318      * Bluetooth is not turned on. When this API returns true, it is guaranteed that connection
319      * state intent for the profile will be broadcasted with the state. Users can get the connection
320      * state of the profile from this intent.
321      *
322      * @param device Remote Bluetooth Device
323      * @return false on immediate error, true otherwise
324      * @hide
325      */
326     @RequiresLegacyBluetoothAdminPermission
327     @RequiresBluetoothConnectPermission
328     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
329     @UnsupportedAppUsage
connect(BluetoothDevice device)330     public boolean connect(BluetoothDevice device) {
331         if (DBG) log("connect(" + device + ")");
332         final IBluetoothA2dp service = getService();
333         if (service == null) {
334             Log.w(TAG, "Proxy not attached to service");
335             if (DBG) log(Log.getStackTraceString(new Throwable()));
336         } else if (isEnabled() && isValidDevice(device)) {
337             try {
338                 return service.connect(device, mAttributionSource);
339             } catch (RemoteException e) {
340                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
341             }
342         }
343         return false;
344     }
345 
346     /**
347      * Initiate disconnection from a profile
348      *
349      * <p>This API will return false in scenarios like the profile on the Bluetooth device is not in
350      * connected state etc. When this API returns, true, it is guaranteed that the connection state
351      * change intent will be broadcasted with the state. Users can get the disconnection state of
352      * the profile from this intent.
353      *
354      * <p>If the disconnection is initiated by a remote device, the state will transition from
355      * {@link #STATE_CONNECTED} to {@link #STATE_DISCONNECTED}. If the disconnect is initiated by
356      * the host (local) device the state will transition from {@link #STATE_CONNECTED} to state
357      * {@link #STATE_DISCONNECTING} to state {@link #STATE_DISCONNECTED}. The transition to {@link
358      * #STATE_DISCONNECTING} can be used to distinguish between the two scenarios.
359      *
360      * @param device Remote Bluetooth Device
361      * @return false on immediate error, true otherwise
362      * @hide
363      */
364     @RequiresLegacyBluetoothAdminPermission
365     @RequiresBluetoothConnectPermission
366     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
367     @UnsupportedAppUsage
disconnect(BluetoothDevice device)368     public boolean disconnect(BluetoothDevice device) {
369         if (DBG) log("disconnect(" + device + ")");
370         final IBluetoothA2dp service = getService();
371         if (service == null) {
372             Log.w(TAG, "Proxy not attached to service");
373             if (DBG) log(Log.getStackTraceString(new Throwable()));
374         } else if (isEnabled() && isValidDevice(device)) {
375             try {
376                 return service.disconnect(device, mAttributionSource);
377             } catch (RemoteException e) {
378                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
379             }
380         }
381         return false;
382     }
383 
384     /** {@inheritDoc} */
385     @Override
386     @RequiresBluetoothConnectPermission
387     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getConnectedDevices()388     public List<BluetoothDevice> getConnectedDevices() {
389         if (VDBG) log("getConnectedDevices()");
390         final IBluetoothA2dp service = getService();
391         if (service == null) {
392             Log.w(TAG, "Proxy not attached to service");
393             if (DBG) log(Log.getStackTraceString(new Throwable()));
394         } else if (isEnabled()) {
395             try {
396                 return Attributable.setAttributionSource(
397                         service.getConnectedDevices(mAttributionSource), mAttributionSource);
398             } catch (RemoteException e) {
399                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
400             }
401         }
402         return Collections.emptyList();
403     }
404 
405     /** {@inheritDoc} */
406     @Override
407     @RequiresBluetoothConnectPermission
408     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getDevicesMatchingConnectionStates(int[] states)409     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
410         if (VDBG) log("getDevicesMatchingStates()");
411         final IBluetoothA2dp service = getService();
412         if (service == null) {
413             Log.w(TAG, "Proxy not attached to service");
414             if (DBG) log(Log.getStackTraceString(new Throwable()));
415         } else if (isEnabled()) {
416             try {
417                 return Attributable.setAttributionSource(
418                         service.getDevicesMatchingConnectionStates(states, mAttributionSource),
419                         mAttributionSource);
420             } catch (RemoteException e) {
421                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
422             }
423         }
424         return Collections.emptyList();
425     }
426 
427     /** {@inheritDoc} */
428     @Override
429     @RequiresBluetoothConnectPermission
430     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getConnectionState(BluetoothDevice device)431     public @BtProfileState int getConnectionState(BluetoothDevice device) {
432         if (VDBG) log("getState(" + device + ")");
433         final IBluetoothA2dp service = getService();
434         if (service == null) {
435             Log.w(TAG, "Proxy not attached to service");
436             if (DBG) log(Log.getStackTraceString(new Throwable()));
437         } else if (isEnabled() && isValidDevice(device)) {
438             try {
439                 return service.getConnectionState(device, mAttributionSource);
440             } catch (RemoteException e) {
441                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
442             }
443         }
444         return BluetoothProfile.STATE_DISCONNECTED;
445     }
446 
447     /**
448      * Select a connected device as active.
449      *
450      * <p>The active device selection is per profile. An active device's purpose is
451      * profile-specific. For example, A2DP audio streaming is to the active A2DP Sink device. If a
452      * remote device is not connected, it cannot be selected as active.
453      *
454      * <p>This API returns false in scenarios like the profile on the device is not connected or
455      * Bluetooth is not turned on. When this API returns true, it is guaranteed that the {@link
456      * #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted with the active device.
457      *
458      * @param device the remote Bluetooth device. Could be null to clear the active device and stop
459      *     streaming audio to a Bluetooth device.
460      * @return false on immediate error, true otherwise
461      * @hide
462      */
463     @RequiresLegacyBluetoothAdminPermission
464     @RequiresBluetoothConnectPermission
465     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
466     @UnsupportedAppUsage(trackingBug = 171933273)
setActiveDevice(@ullable BluetoothDevice device)467     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
468         if (DBG) log("setActiveDevice(" + device + ")");
469         final IBluetoothA2dp service = getService();
470         if (service == null) {
471             Log.w(TAG, "Proxy not attached to service");
472             if (DBG) log(Log.getStackTraceString(new Throwable()));
473         } else if (isEnabled() && ((device == null) || isValidDevice(device))) {
474             try {
475                 return service.setActiveDevice(device, mAttributionSource);
476             } catch (RemoteException e) {
477                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
478             }
479         }
480         return false;
481     }
482 
483     /**
484      * Get the connected device that is active.
485      *
486      * @return the connected device that is active or null if no device is active
487      * @hide
488      */
489     @UnsupportedAppUsage(trackingBug = 171933273)
490     @Nullable
491     @RequiresLegacyBluetoothPermission
492     @RequiresBluetoothConnectPermission
493     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getActiveDevice()494     public BluetoothDevice getActiveDevice() {
495         if (VDBG) log("getActiveDevice()");
496         final IBluetoothA2dp service = getService();
497         if (service == null) {
498             Log.w(TAG, "Proxy not attached to service");
499             if (DBG) log(Log.getStackTraceString(new Throwable()));
500         } else if (isEnabled()) {
501             try {
502                 return Attributable.setAttributionSource(
503                         service.getActiveDevice(mAttributionSource), mAttributionSource);
504             } catch (RemoteException e) {
505                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
506             }
507         }
508         return null;
509     }
510 
511     /**
512      * Set priority of the profile
513      *
514      * <p>The device should already be paired. Priority can be one of {@link #PRIORITY_ON} or {@link
515      * #PRIORITY_OFF}
516      *
517      * @param device Paired bluetooth device
518      * @return true if priority is set, false on error
519      * @hide
520      */
521     @RequiresBluetoothConnectPermission
522     @RequiresPermission(
523             allOf = {
524                 android.Manifest.permission.BLUETOOTH_CONNECT,
525                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
526             })
setPriority(BluetoothDevice device, int priority)527     public boolean setPriority(BluetoothDevice device, int priority) {
528         if (DBG) log("setPriority(" + device + ", " + priority + ")");
529         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
530     }
531 
532     /**
533      * Set connection policy of the profile
534      *
535      * <p>The device should already be paired. Connection policy can be one of {@link
536      * #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, {@link
537      * #CONNECTION_POLICY_UNKNOWN}
538      *
539      * @param device Paired bluetooth device
540      * @param connectionPolicy is the connection policy to set to for this profile
541      * @return true if connectionPolicy is set, false on error
542      * @hide
543      */
544     @SystemApi
545     @RequiresBluetoothConnectPermission
546     @RequiresPermission(
547             allOf = {
548                 android.Manifest.permission.BLUETOOTH_CONNECT,
549                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
550             })
setConnectionPolicy( @onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)551     public boolean setConnectionPolicy(
552             @NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) {
553         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
554         final IBluetoothA2dp service = getService();
555         if (service == null) {
556             Log.w(TAG, "Proxy not attached to service");
557             if (DBG) log(Log.getStackTraceString(new Throwable()));
558         } else if (isEnabled()
559                 && isValidDevice(device)
560                 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
561                         || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
562             try {
563                 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
564             } catch (RemoteException e) {
565                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
566             }
567         }
568         return false;
569     }
570 
571     /**
572      * Get the priority of the profile.
573      *
574      * <p>The priority can be any of: {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link
575      * #PRIORITY_UNDEFINED}
576      *
577      * @param device Bluetooth device
578      * @return priority of the device
579      * @hide
580      */
581     @RequiresLegacyBluetoothPermission
582     @RequiresBluetoothConnectPermission
583     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
584     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
getPriority(BluetoothDevice device)585     public int getPriority(BluetoothDevice device) {
586         if (VDBG) log("getPriority(" + device + ")");
587         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
588     }
589 
590     /**
591      * Get the connection policy of the profile.
592      *
593      * <p>The connection policy can be any of: {@link #CONNECTION_POLICY_ALLOWED}, {@link
594      * #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
595      *
596      * @param device Bluetooth device
597      * @return connection policy of the device
598      * @hide
599      */
600     @SystemApi
601     @RequiresBluetoothConnectPermission
602     @RequiresPermission(
603             allOf = {
604                 android.Manifest.permission.BLUETOOTH_CONNECT,
605                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
606             })
getConnectionPolicy(@onNull BluetoothDevice device)607     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
608         if (VDBG) log("getConnectionPolicy(" + device + ")");
609         final IBluetoothA2dp service = getService();
610         if (service == null) {
611             Log.w(TAG, "Proxy not attached to service");
612             if (DBG) log(Log.getStackTraceString(new Throwable()));
613         } else if (isEnabled() && isValidDevice(device)) {
614             try {
615                 return service.getConnectionPolicy(device, mAttributionSource);
616             } catch (RemoteException e) {
617                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
618             }
619         }
620         return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
621     }
622 
623     /**
624      * Tells remote device to set an absolute volume. Only if absolute volume is supported
625      *
626      * @param volume Absolute volume to be set on AVRCP side
627      * @hide
628      */
629     @SystemApi
630     @RequiresBluetoothConnectPermission
631     @RequiresPermission(
632             allOf = {
633                 android.Manifest.permission.BLUETOOTH_CONNECT,
634                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
635             })
setAvrcpAbsoluteVolume(int volume)636     public void setAvrcpAbsoluteVolume(int volume) {
637         if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume");
638         final IBluetoothA2dp service = getService();
639         if (service == null) {
640             Log.w(TAG, "Proxy not attached to service");
641             if (DBG) log(Log.getStackTraceString(new Throwable()));
642         } else if (isEnabled()) {
643             try {
644                 service.setAvrcpAbsoluteVolume(volume, mAttributionSource);
645             } catch (RemoteException e) {
646                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
647             }
648         }
649     }
650 
651     /**
652      * Check if A2DP profile is streaming music.
653      *
654      * @param device BluetoothDevice device
655      */
656     @RequiresLegacyBluetoothPermission
657     @RequiresBluetoothConnectPermission
658     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
isA2dpPlaying(BluetoothDevice device)659     public boolean isA2dpPlaying(BluetoothDevice device) {
660         if (DBG) log("isA2dpPlaying(" + device + ")");
661         final IBluetoothA2dp service = getService();
662         if (service == null) {
663             Log.w(TAG, "Proxy not attached to service");
664             if (DBG) log(Log.getStackTraceString(new Throwable()));
665         } else if (isEnabled() && isValidDevice(device)) {
666             try {
667                 return service.isA2dpPlaying(device, mAttributionSource);
668             } catch (RemoteException e) {
669                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
670             }
671         }
672         return false;
673     }
674 
675     /**
676      * This function checks if the remote device is an AVCRP target and thus whether we should send
677      * volume keys changes or not.
678      *
679      * @hide
680      */
681     @RequiresBluetoothConnectPermission
682     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
shouldSendVolumeKeys(BluetoothDevice device)683     public boolean shouldSendVolumeKeys(BluetoothDevice device) {
684         if (isEnabled() && isValidDevice(device)) {
685             ParcelUuid[] uuids = device.getUuids();
686             if (uuids == null) return false;
687 
688             for (ParcelUuid uuid : uuids) {
689                 if (uuid.equals(BluetoothUuid.AVRCP_TARGET)) {
690                     return true;
691                 }
692             }
693         }
694         return false;
695     }
696 
697     /**
698      * Returns the list of source codecs that are supported by the current platform.
699      *
700      * <p>The list always includes the mandatory SBC codec, and may include optional proprietary
701      * codecs.
702      *
703      * @return list of supported source codec types
704      */
705     @NonNull
706     @RequiresLegacyBluetoothPermission
707     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
708     @FlaggedApi(Flags.FLAG_A2DP_OFFLOAD_CODEC_EXTENSIBILITY)
getSupportedCodecTypes()709     public Collection<BluetoothCodecType> getSupportedCodecTypes() {
710         Log.d(TAG, "getSupportedSourceCodecTypes()");
711         final IBluetoothA2dp service = getService();
712         if (service == null) {
713             Log.w(TAG, "Proxy not attached to service");
714             if (DBG) log(Log.getStackTraceString(new Throwable()));
715         } else if (isEnabled()) {
716             try {
717                 return service.getSupportedCodecTypes(mAttributionSource);
718             } catch (RemoteException e) {
719                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
720             }
721         }
722         return Collections.emptyList();
723     }
724 
725     /**
726      * Gets the current codec status (configuration and capability).
727      *
728      * @param device the remote Bluetooth device.
729      * @return the current codec status
730      * @hide
731      */
732     @SystemApi
733     @Nullable
734     @RequiresLegacyBluetoothPermission
735     @RequiresBluetoothConnectPermission
736     @RequiresPermission(
737             allOf = {
738                 android.Manifest.permission.BLUETOOTH_CONNECT,
739                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
740             })
getCodecStatus(@onNull BluetoothDevice device)741     public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) {
742         if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
743         verifyDeviceNotNull(device, "getCodecStatus");
744         final IBluetoothA2dp service = getService();
745         if (service == null) {
746             Log.w(TAG, "Proxy not attached to service");
747             if (DBG) log(Log.getStackTraceString(new Throwable()));
748         } else if (isEnabled() && isValidDevice(device)) {
749             try {
750                 return service.getCodecStatus(device, mAttributionSource);
751             } catch (RemoteException e) {
752                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
753             }
754         }
755         return null;
756     }
757 
758     /**
759      * Sets the codec configuration preference.
760      *
761      * <p>For apps without the {@link android.Manifest.permission.BLUETOOTH_PRIVILEGED} permission a
762      * {@link android.companion.CompanionDeviceManager} association is required.
763      *
764      * @param device the remote Bluetooth device.
765      * @param codecConfig the codec configuration preference
766      * @hide
767      */
768     @SystemApi
769     @RequiresLegacyBluetoothPermission
770     @RequiresBluetoothConnectPermission
771     @RequiresPermission(
772             allOf = {
773                 android.Manifest.permission.BLUETOOTH_CONNECT,
774                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
775             })
setCodecConfigPreference( @onNull BluetoothDevice device, @NonNull BluetoothCodecConfig codecConfig)776     public void setCodecConfigPreference(
777             @NonNull BluetoothDevice device, @NonNull BluetoothCodecConfig codecConfig) {
778         if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")");
779         verifyDeviceNotNull(device, "setCodecConfigPreference");
780         if (codecConfig == null) {
781             Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
782             throw new IllegalArgumentException("codecConfig cannot be null");
783         }
784         final IBluetoothA2dp service = getService();
785         if (service == null) {
786             Log.w(TAG, "Proxy not attached to service");
787             if (DBG) log(Log.getStackTraceString(new Throwable()));
788         } else if (isEnabled()) {
789             try {
790                 service.setCodecConfigPreference(device, codecConfig, mAttributionSource);
791             } catch (RemoteException e) {
792                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
793             }
794         }
795     }
796 
797     /**
798      * Enables the optional codecs for the given device for this connection.
799      *
800      * <p>If the given device supports another codec type than {@link
801      * BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC}, this will switch to it. Switching from one codec
802      * to another will create a short audio drop. In case of multiple applications calling the
803      * method, the last call will be taken into account, overriding any previous call
804      *
805      * <p>See {@link #setOptionalCodecsEnabled} to enable optional codecs by default when the given
806      * device is connected.
807      *
808      * @param device the remote Bluetooth device
809      * @hide
810      */
811     @SystemApi
812     @RequiresLegacyBluetoothPermission
813     @RequiresBluetoothConnectPermission
814     @RequiresPermission(
815             allOf = {
816                 android.Manifest.permission.BLUETOOTH_CONNECT,
817                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
818             })
enableOptionalCodecs(@onNull BluetoothDevice device)819     public void enableOptionalCodecs(@NonNull BluetoothDevice device) {
820         if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")");
821         verifyDeviceNotNull(device, "enableOptionalCodecs");
822         enableDisableOptionalCodecs(device, true);
823     }
824 
825     /**
826      * Disables the optional codecs for the given device for this connection.
827      *
828      * <p>When optional codecs are disabled, the device will use the default Bluetooth audio codec
829      * type. Switching from one codec to another will create a short audio drop. In case of multiple
830      * applications calling the method, the last call will be taken into account, overriding any
831      * previous call
832      *
833      * <p>See {@link BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC}. See {@link
834      * #setOptionalCodecsEnabled} to disable optional codecs by default when the given device is
835      * connected.
836      *
837      * @param device the remote Bluetooth device
838      * @hide
839      */
840     @SystemApi
841     @RequiresLegacyBluetoothPermission
842     @RequiresBluetoothConnectPermission
843     @RequiresPermission(
844             allOf = {
845                 android.Manifest.permission.BLUETOOTH_CONNECT,
846                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
847             })
disableOptionalCodecs(@onNull BluetoothDevice device)848     public void disableOptionalCodecs(@NonNull BluetoothDevice device) {
849         if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")");
850         verifyDeviceNotNull(device, "disableOptionalCodecs");
851         enableDisableOptionalCodecs(device, false);
852     }
853 
854     /**
855      * Enables or disables the optional codecs.
856      *
857      * @param device the remote Bluetooth device.
858      * @param enable if true, enable the optional codecs, otherwise disable them
859      */
860     @RequiresPermission(
861             allOf = {
862                 android.Manifest.permission.BLUETOOTH_CONNECT,
863                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
864             })
enableDisableOptionalCodecs(BluetoothDevice device, boolean enable)865     private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) {
866         final IBluetoothA2dp service = getService();
867         if (service == null) {
868             Log.w(TAG, "Proxy not attached to service");
869             if (DBG) log(Log.getStackTraceString(new Throwable()));
870         } else if (isEnabled() && isValidDevice(device)) {
871             try {
872                 if (enable) {
873                     service.enableOptionalCodecs(device, mAttributionSource);
874                 } else {
875                     service.disableOptionalCodecs(device, mAttributionSource);
876                 }
877             } catch (RemoteException e) {
878                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
879             }
880         }
881     }
882 
883     /**
884      * Returns whether this device supports optional codecs.
885      *
886      * @param device the remote Bluetooth device
887      * @return whether the optional codecs are supported or not, or {@link
888      *     #OPTIONAL_CODECS_SUPPORT_UNKNOWN} if the state can't be retrieved.
889      * @hide
890      */
891     @SystemApi
892     @RequiresLegacyBluetoothAdminPermission
893     @RequiresBluetoothConnectPermission
894     @RequiresPermission(
895             allOf = {
896                 android.Manifest.permission.BLUETOOTH_CONNECT,
897                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
898             })
899     @OptionalCodecsSupportStatus
isOptionalCodecsSupported(@onNull BluetoothDevice device)900     public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) {
901         if (DBG) log("isOptionalCodecsSupported(" + device + ")");
902         verifyDeviceNotNull(device, "isOptionalCodecsSupported");
903         final IBluetoothA2dp service = getService();
904         if (service == null) {
905             Log.w(TAG, "Proxy not attached to service");
906             if (DBG) log(Log.getStackTraceString(new Throwable()));
907         } else if (isEnabled() && isValidDevice(device)) {
908             try {
909                 return service.isOptionalCodecsSupported(device, mAttributionSource);
910             } catch (RemoteException e) {
911                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
912             }
913         }
914         return OPTIONAL_CODECS_SUPPORT_UNKNOWN;
915     }
916 
917     /**
918      * Returns whether this device has its optional codecs enabled.
919      *
920      * @param device the remote Bluetooth device
921      * @return whether the optional codecs are enabled or not, or {@link
922      *     #OPTIONAL_CODECS_PREF_UNKNOWN} if the state can't be retrieved.
923      * @hide
924      */
925     @SystemApi
926     @RequiresLegacyBluetoothAdminPermission
927     @RequiresBluetoothConnectPermission
928     @RequiresPermission(
929             allOf = {
930                 android.Manifest.permission.BLUETOOTH_CONNECT,
931                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
932             })
933     @OptionalCodecsPreferenceStatus
isOptionalCodecsEnabled(@onNull BluetoothDevice device)934     public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) {
935         if (DBG) log("isOptionalCodecsEnabled(" + device + ")");
936         verifyDeviceNotNull(device, "isOptionalCodecsEnabled");
937         final IBluetoothA2dp service = getService();
938         if (service == null) {
939             Log.w(TAG, "Proxy not attached to service");
940             if (DBG) log(Log.getStackTraceString(new Throwable()));
941         } else if (isEnabled() && isValidDevice(device)) {
942             try {
943                 return service.isOptionalCodecsEnabled(device, mAttributionSource);
944             } catch (RemoteException e) {
945                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
946             }
947         }
948         return OPTIONAL_CODECS_PREF_UNKNOWN;
949     }
950 
951     /**
952      * Sets the default state of optional codecs for the given device.
953      *
954      * <p>Automatically enables or disables the optional codecs for the given device when connected.
955      *
956      * @param device the remote Bluetooth device
957      * @param value whether the optional codecs should be enabled for this device
958      * @hide
959      */
960     @SystemApi
961     @RequiresLegacyBluetoothAdminPermission
962     @RequiresBluetoothConnectPermission
963     @RequiresPermission(
964             allOf = {
965                 android.Manifest.permission.BLUETOOTH_CONNECT,
966                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
967             })
setOptionalCodecsEnabled( @onNull BluetoothDevice device, @OptionalCodecsPreferenceStatus int value)968     public void setOptionalCodecsEnabled(
969             @NonNull BluetoothDevice device, @OptionalCodecsPreferenceStatus int value) {
970         if (DBG) log("setOptionalCodecsEnabled(" + device + ")");
971         verifyDeviceNotNull(device, "setOptionalCodecsEnabled");
972         if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
973                 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
974                 && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
975             Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value);
976             return;
977         }
978         final IBluetoothA2dp service = getService();
979         if (service == null) {
980             Log.w(TAG, "Proxy not attached to service");
981             if (DBG) log(Log.getStackTraceString(new Throwable()));
982         } else if (isEnabled() && isValidDevice(device)) {
983             try {
984                 service.setOptionalCodecsEnabled(device, value, mAttributionSource);
985             } catch (RemoteException e) {
986                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
987             }
988         }
989     }
990 
991     /**
992      * Get the supported type of the Dynamic Audio Buffer.
993      *
994      * <p>Possible return values are {@link #DYNAMIC_BUFFER_SUPPORT_NONE}, {@link
995      * #DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD}, {@link
996      * #DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING}.
997      *
998      * @return supported type of Dynamic Audio Buffer feature
999      * @hide
1000      */
1001     @SystemApi
1002     @RequiresBluetoothConnectPermission
1003     @RequiresPermission(
1004             allOf = {
1005                 android.Manifest.permission.BLUETOOTH_CONNECT,
1006                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1007             })
getDynamicBufferSupport()1008     public @Type int getDynamicBufferSupport() {
1009         if (VDBG) log("getDynamicBufferSupport()");
1010         final IBluetoothA2dp service = getService();
1011         if (service == null) {
1012             Log.w(TAG, "Proxy not attached to service");
1013             if (DBG) log(Log.getStackTraceString(new Throwable()));
1014         } else if (isEnabled()) {
1015             try {
1016                 return service.getDynamicBufferSupport(mAttributionSource);
1017             } catch (RemoteException e) {
1018                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1019             }
1020         }
1021         return DYNAMIC_BUFFER_SUPPORT_NONE;
1022     }
1023 
1024     /**
1025      * Return the record of {@link BufferConstraints} object that has the default/maximum/minimum
1026      * audio buffer. This can be used to inform what the controller has support for the audio
1027      * buffer.
1028      *
1029      * @return a record with {@link BufferConstraints} or null if report is unavailable or
1030      *     unsupported
1031      * @hide
1032      */
1033     @SystemApi
1034     @RequiresBluetoothConnectPermission
1035     @RequiresPermission(
1036             allOf = {
1037                 android.Manifest.permission.BLUETOOTH_CONNECT,
1038                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1039             })
getBufferConstraints()1040     public @Nullable BufferConstraints getBufferConstraints() {
1041         if (VDBG) log("getBufferConstraints()");
1042         final IBluetoothA2dp service = getService();
1043         if (service == null) {
1044             Log.w(TAG, "Proxy not attached to service");
1045             if (DBG) log(Log.getStackTraceString(new Throwable()));
1046         } else if (isEnabled()) {
1047             try {
1048                 return service.getBufferConstraints(mAttributionSource);
1049             } catch (RemoteException e) {
1050                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1051             }
1052         }
1053         return null;
1054     }
1055 
1056     /**
1057      * Set Dynamic Audio Buffer Size.
1058      *
1059      * @param codec audio codec
1060      * @param value buffer millis
1061      * @return true to indicate success, or false on immediate error
1062      * @hide
1063      */
1064     @SystemApi
1065     @RequiresBluetoothConnectPermission
1066     @RequiresPermission(
1067             allOf = {
1068                 android.Manifest.permission.BLUETOOTH_CONNECT,
1069                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
1070             })
setBufferLengthMillis( @luetoothCodecConfig.SourceCodecType int codec, int value)1071     public boolean setBufferLengthMillis(
1072             @BluetoothCodecConfig.SourceCodecType int codec, int value) {
1073         if (VDBG) log("setBufferLengthMillis(" + codec + ", " + value + ")");
1074         if (value < 0) {
1075             Log.e(TAG, "Trying to set audio buffer length to a negative value: " + value);
1076             return false;
1077         }
1078         final IBluetoothA2dp service = getService();
1079         if (service == null) {
1080             Log.w(TAG, "Proxy not attached to service");
1081             if (DBG) log(Log.getStackTraceString(new Throwable()));
1082         } else if (isEnabled()) {
1083             try {
1084                 return service.setBufferLengthMillis(codec, value, mAttributionSource);
1085             } catch (RemoteException e) {
1086                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
1087             }
1088         }
1089         return false;
1090     }
1091 
1092     /**
1093      * Helper for converting a state to a string.
1094      *
1095      * <p>For debug use only - strings are not internationalized.
1096      *
1097      * @hide
1098      */
1099     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
stateToString(int state)1100     public static String stateToString(int state) {
1101         switch (state) {
1102             case STATE_DISCONNECTED:
1103                 return "disconnected";
1104             case STATE_CONNECTING:
1105                 return "connecting";
1106             case STATE_CONNECTED:
1107                 return "connected";
1108             case STATE_DISCONNECTING:
1109                 return "disconnecting";
1110             case STATE_PLAYING:
1111                 return "playing";
1112             case STATE_NOT_PLAYING:
1113                 return "not playing";
1114             default:
1115                 return "<unknown state " + state + ">";
1116         }
1117     }
1118 
isEnabled()1119     private boolean isEnabled() {
1120         if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
1121         return false;
1122     }
1123 
verifyDeviceNotNull(BluetoothDevice device, String methodName)1124     private void verifyDeviceNotNull(BluetoothDevice device, String methodName) {
1125         if (device == null) {
1126             Log.e(TAG, methodName + ": device param is null");
1127             throw new IllegalArgumentException("Device cannot be null");
1128         }
1129     }
1130 
isValidDevice(BluetoothDevice device)1131     private boolean isValidDevice(BluetoothDevice device) {
1132         if (device == null) return false;
1133 
1134         if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
1135         return false;
1136     }
1137 
log(String msg)1138     private static void log(String msg) {
1139         Log.d(TAG, msg);
1140     }
1141 }
1142