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