Home | History | Annotate | Download | only in bluetooth
      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      */
    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 
    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 
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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)
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    824     private boolean isEnabled() {
    825        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
    826        return false;
    827     }
    828 
    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 
    836     private static void log(String msg) {
    837       Log.d(TAG, msg);
    838     }
    839 }
    840