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
17package android.bluetooth;
18
19import android.Manifest;
20import android.annotation.Nullable;
21import android.annotation.RequiresPermission;
22import android.annotation.SdkConstant;
23import android.annotation.SdkConstant.SdkConstantType;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.ServiceConnection;
28import android.os.Binder;
29import android.os.IBinder;
30import android.os.ParcelUuid;
31import android.os.RemoteException;
32import android.util.Log;
33
34import com.android.internal.annotations.GuardedBy;
35
36import java.util.ArrayList;
37import java.util.List;
38import 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 */
52public 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     */
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
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
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
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     */
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     */
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
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
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
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     */
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
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     */
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)
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
905    private boolean isEnabled() {
906        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
907        return false;
908    }
909
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
917    private static void log(String msg) {
918        Log.d(TAG, msg);
919    }
920}
921