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