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