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