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