• Home
  • History
  • Annotate
  • 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