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.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      */
    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