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