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.annotation.SdkConstant;
     20 import android.annotation.SdkConstant.SdkConstantType;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.ServiceConnection;
     25 import android.os.IBinder;
     26 import android.os.ParcelUuid;
     27 import android.os.RemoteException;
     28 import android.util.Log;
     29 
     30 import java.util.ArrayList;
     31 import java.util.List;
     32 
     33 
     34 /**
     35  * This class provides the public APIs to control the Bluetooth A2DP
     36  * profile.
     37  *
     38  *<p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
     39  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
     40  * the BluetoothA2dp proxy object.
     41  *
     42  * <p> Android only supports one connected Bluetooth A2dp device at a time.
     43  * Each method is protected with its appropriate permission.
     44  */
     45 public final class BluetoothA2dp implements BluetoothProfile {
     46     private static final String TAG = "BluetoothA2dp";
     47     private static final boolean DBG = true;
     48     private static final boolean VDBG = false;
     49 
     50     /**
     51      * Intent used to broadcast the change in connection state of the A2DP
     52      * profile.
     53      *
     54      * <p>This intent will have 3 extras:
     55      * <ul>
     56      *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
     57      *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
     58      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
     59      * </ul>
     60      *
     61      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
     62      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
     63      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
     64      *
     65      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
     66      * receive.
     67      */
     68     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     69     public static final String ACTION_CONNECTION_STATE_CHANGED =
     70         "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
     71 
     72     /**
     73      * Intent used to broadcast the change in the Playing state of the A2DP
     74      * profile.
     75      *
     76      * <p>This intent will have 3 extras:
     77      * <ul>
     78      *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
     79      *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
     80      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
     81      * </ul>
     82      *
     83      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
     84      * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
     85      *
     86      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
     87      * receive.
     88      */
     89     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     90     public static final String ACTION_PLAYING_STATE_CHANGED =
     91         "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
     92 
     93     /** @hide */
     94     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     95     public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED =
     96         "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED";
     97 
     98     /**
     99      * A2DP sink device is streaming music. This state can be one of
    100      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
    101      * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
    102      */
    103     public static final int STATE_PLAYING   =  10;
    104 
    105     /**
    106      * A2DP sink device is NOT streaming music. This state can be one of
    107      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
    108      * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
    109      */
    110     public static final int STATE_NOT_PLAYING   =  11;
    111 
    112     private Context mContext;
    113     private ServiceListener mServiceListener;
    114     private IBluetoothA2dp mService;
    115     private BluetoothAdapter mAdapter;
    116 
    117     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
    118             new IBluetoothStateChangeCallback.Stub() {
    119                 public void onBluetoothStateChange(boolean up) {
    120                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
    121                     if (!up) {
    122                         if (VDBG) Log.d(TAG,"Unbinding service...");
    123                         synchronized (mConnection) {
    124                             try {
    125                                 mService = null;
    126                                 mContext.unbindService(mConnection);
    127                             } catch (Exception re) {
    128                                 Log.e(TAG,"",re);
    129                             }
    130                         }
    131                     } else {
    132                         synchronized (mConnection) {
    133                             try {
    134                                 if (mService == null) {
    135                                     if (VDBG) Log.d(TAG,"Binding service...");
    136                                     doBind();
    137                                 }
    138                             } catch (Exception re) {
    139                                 Log.e(TAG,"",re);
    140                             }
    141                         }
    142                     }
    143                 }
    144         };
    145     /**
    146      * Create a BluetoothA2dp proxy object for interacting with the local
    147      * Bluetooth A2DP service.
    148      *
    149      */
    150     /*package*/ BluetoothA2dp(Context context, ServiceListener l) {
    151         mContext = context;
    152         mServiceListener = l;
    153         mAdapter = BluetoothAdapter.getDefaultAdapter();
    154         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    155         if (mgr != null) {
    156             try {
    157                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
    158             } catch (RemoteException e) {
    159                 Log.e(TAG,"",e);
    160             }
    161         }
    162 
    163         doBind();
    164     }
    165 
    166     boolean doBind() {
    167         Intent intent = new Intent(IBluetoothA2dp.class.getName());
    168         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
    169         intent.setComponent(comp);
    170         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
    171                 android.os.Process.myUserHandle())) {
    172             Log.e(TAG, "Could not bind to Bluetooth A2DP Service with " + intent);
    173             return false;
    174         }
    175         return true;
    176     }
    177 
    178     /*package*/ void close() {
    179         mServiceListener = null;
    180         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    181         if (mgr != null) {
    182             try {
    183                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
    184             } catch (Exception e) {
    185                 Log.e(TAG,"",e);
    186             }
    187         }
    188 
    189         synchronized (mConnection) {
    190             if (mService != null) {
    191                 try {
    192                     mService = null;
    193                     mContext.unbindService(mConnection);
    194                 } catch (Exception re) {
    195                     Log.e(TAG,"",re);
    196                 }
    197             }
    198         }
    199     }
    200 
    201     public void finalize() {
    202         close();
    203     }
    204     /**
    205      * Initiate connection to a profile of the remote bluetooth device.
    206      *
    207      * <p> Currently, the system supports only 1 connection to the
    208      * A2DP profile. The API will automatically disconnect connected
    209      * devices before connecting.
    210      *
    211      * <p> This API returns false in scenarios like the profile on the
    212      * device is already connected or Bluetooth is not turned on.
    213      * When this API returns true, it is guaranteed that
    214      * connection state intent for the profile will be broadcasted with
    215      * the state. Users can get the connection state of the profile
    216      * from this intent.
    217      *
    218      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    219      * permission.
    220      *
    221      * @param device Remote Bluetooth Device
    222      * @return false on immediate error,
    223      *               true otherwise
    224      * @hide
    225      */
    226     public boolean connect(BluetoothDevice device) {
    227         if (DBG) log("connect(" + device + ")");
    228         if (mService != null && isEnabled() &&
    229             isValidDevice(device)) {
    230             try {
    231                 return mService.connect(device);
    232             } catch (RemoteException e) {
    233                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    234                 return false;
    235             }
    236         }
    237         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    238         return false;
    239     }
    240 
    241     /**
    242      * Initiate disconnection from a profile
    243      *
    244      * <p> This API will return false in scenarios like the profile on the
    245      * Bluetooth device is not in connected state etc. When this API returns,
    246      * true, it is guaranteed that the connection state change
    247      * intent will be broadcasted with the state. Users can get the
    248      * disconnection state of the profile from this intent.
    249      *
    250      * <p> If the disconnection is initiated by a remote device, the state
    251      * will transition from {@link #STATE_CONNECTED} to
    252      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
    253      * host (local) device the state will transition from
    254      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
    255      * state {@link #STATE_DISCONNECTED}. The transition to
    256      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
    257      * two scenarios.
    258      *
    259      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    260      * permission.
    261      *
    262      * @param device Remote Bluetooth Device
    263      * @return false on immediate error,
    264      *               true otherwise
    265      * @hide
    266      */
    267     public boolean disconnect(BluetoothDevice device) {
    268         if (DBG) log("disconnect(" + device + ")");
    269         if (mService != null && isEnabled() &&
    270             isValidDevice(device)) {
    271             try {
    272                 return mService.disconnect(device);
    273             } catch (RemoteException e) {
    274                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    275                 return false;
    276             }
    277         }
    278         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    279         return false;
    280     }
    281 
    282     /**
    283      * {@inheritDoc}
    284      */
    285     public List<BluetoothDevice> getConnectedDevices() {
    286         if (VDBG) log("getConnectedDevices()");
    287         if (mService != null && isEnabled()) {
    288             try {
    289                 return mService.getConnectedDevices();
    290             } catch (RemoteException e) {
    291                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    292                 return new ArrayList<BluetoothDevice>();
    293             }
    294         }
    295         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    296         return new ArrayList<BluetoothDevice>();
    297     }
    298 
    299     /**
    300      * {@inheritDoc}
    301      */
    302     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    303         if (VDBG) log("getDevicesMatchingStates()");
    304         if (mService != null && isEnabled()) {
    305             try {
    306                 return mService.getDevicesMatchingConnectionStates(states);
    307             } catch (RemoteException e) {
    308                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    309                 return new ArrayList<BluetoothDevice>();
    310             }
    311         }
    312         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    313         return new ArrayList<BluetoothDevice>();
    314     }
    315 
    316     /**
    317      * {@inheritDoc}
    318      */
    319     public int getConnectionState(BluetoothDevice device) {
    320         if (VDBG) log("getState(" + device + ")");
    321         if (mService != null && isEnabled()
    322             && isValidDevice(device)) {
    323             try {
    324                 return mService.getConnectionState(device);
    325             } catch (RemoteException e) {
    326                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    327                 return BluetoothProfile.STATE_DISCONNECTED;
    328             }
    329         }
    330         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    331         return BluetoothProfile.STATE_DISCONNECTED;
    332     }
    333 
    334     /**
    335      * Set priority of the profile
    336      *
    337      * <p> The device should already be paired.
    338      *  Priority can be one of {@link #PRIORITY_ON} orgetBluetoothManager
    339      * {@link #PRIORITY_OFF},
    340      *
    341      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    342      * permission.
    343      *
    344      * @param device Paired bluetooth device
    345      * @param priority
    346      * @return true if priority is set, false on error
    347      * @hide
    348      */
    349     public boolean setPriority(BluetoothDevice device, int priority) {
    350         if (DBG) log("setPriority(" + device + ", " + priority + ")");
    351         if (mService != null && isEnabled()
    352             && isValidDevice(device)) {
    353             if (priority != BluetoothProfile.PRIORITY_OFF &&
    354                 priority != BluetoothProfile.PRIORITY_ON){
    355               return false;
    356             }
    357             try {
    358                 return mService.setPriority(device, priority);
    359             } catch (RemoteException e) {
    360                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    361                 return false;
    362             }
    363         }
    364         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    365         return false;
    366     }
    367 
    368     /**
    369      * Get the priority of the profile.
    370      *
    371      * <p> The priority can be any of:
    372      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
    373      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
    374      *
    375      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    376      *
    377      * @param device Bluetooth device
    378      * @return priority of the device
    379      * @hide
    380      */
    381     public int getPriority(BluetoothDevice device) {
    382         if (VDBG) log("getPriority(" + device + ")");
    383         if (mService != null && isEnabled()
    384             && isValidDevice(device)) {
    385             try {
    386                 return mService.getPriority(device);
    387             } catch (RemoteException e) {
    388                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    389                 return BluetoothProfile.PRIORITY_OFF;
    390             }
    391         }
    392         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    393         return BluetoothProfile.PRIORITY_OFF;
    394     }
    395 
    396     /**
    397      * Checks if Avrcp device supports the absolute volume feature.
    398      *
    399      * @return true if device supports absolute volume
    400      * @hide
    401      */
    402     public boolean isAvrcpAbsoluteVolumeSupported() {
    403         if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported");
    404         if (mService != null && isEnabled()) {
    405             try {
    406                 return mService.isAvrcpAbsoluteVolumeSupported();
    407             } catch (RemoteException e) {
    408                 Log.e(TAG, "Error talking to BT service in isAvrcpAbsoluteVolumeSupported()", e);
    409                 return false;
    410             }
    411         }
    412         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    413         return false;
    414     }
    415 
    416     /**
    417      * Tells remote device to adjust volume. Only if absolute volume is supported.
    418      *
    419      * @param direction 1 to increase volume, or -1 to decrease volume
    420      * @hide
    421      */
    422     public void adjustAvrcpAbsoluteVolume(int direction) {
    423         if (DBG) Log.d(TAG, "adjustAvrcpAbsoluteVolume");
    424         if (mService != null && isEnabled()) {
    425             try {
    426                 mService.adjustAvrcpAbsoluteVolume(direction);
    427                 return;
    428             } catch (RemoteException e) {
    429                 Log.e(TAG, "Error talking to BT service in adjustAvrcpAbsoluteVolume()", e);
    430                 return;
    431             }
    432         }
    433         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    434     }
    435 
    436     /**
    437      * Tells remote device to set an absolute volume. Only if absolute volume is supported
    438      *
    439      * @param volume Absolute volume to be set on AVRCP side
    440      * @hide
    441      */
    442     public void setAvrcpAbsoluteVolume(int volume) {
    443         if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume");
    444         if (mService != null && isEnabled()) {
    445             try {
    446                 mService.setAvrcpAbsoluteVolume(volume);
    447                 return;
    448             } catch (RemoteException e) {
    449                 Log.e(TAG, "Error talking to BT service in setAvrcpAbsoluteVolume()", e);
    450                 return;
    451             }
    452         }
    453         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    454     }
    455 
    456     /**
    457      * Check if A2DP profile is streaming music.
    458      *
    459      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    460      *
    461      * @param device BluetoothDevice device
    462      */
    463     public boolean isA2dpPlaying(BluetoothDevice device) {
    464         if (mService != null && isEnabled()
    465             && isValidDevice(device)) {
    466             try {
    467                 return mService.isA2dpPlaying(device);
    468             } catch (RemoteException e) {
    469                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    470                 return false;
    471             }
    472         }
    473         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    474         return false;
    475     }
    476 
    477     /**
    478      * This function checks if the remote device is an AVCRP
    479      * target and thus whether we should send volume keys
    480      * changes or not.
    481      * @hide
    482      */
    483     public boolean shouldSendVolumeKeys(BluetoothDevice device) {
    484         if (isEnabled() && isValidDevice(device)) {
    485             ParcelUuid[] uuids = device.getUuids();
    486             if (uuids == null) return false;
    487 
    488             for (ParcelUuid uuid: uuids) {
    489                 if (BluetoothUuid.isAvrcpTarget(uuid)) {
    490                     return true;
    491                 }
    492             }
    493         }
    494         return false;
    495     }
    496 
    497     /**
    498      * Helper for converting a state to a string.
    499      *
    500      * For debug use only - strings are not internationalized.
    501      * @hide
    502      */
    503     public static String stateToString(int state) {
    504         switch (state) {
    505         case STATE_DISCONNECTED:
    506             return "disconnected";
    507         case STATE_CONNECTING:
    508             return "connecting";
    509         case STATE_CONNECTED:
    510             return "connected";
    511         case STATE_DISCONNECTING:
    512             return "disconnecting";
    513         case STATE_PLAYING:
    514             return "playing";
    515         case STATE_NOT_PLAYING:
    516           return "not playing";
    517         default:
    518             return "<unknown state " + state + ">";
    519         }
    520     }
    521 
    522     private final ServiceConnection mConnection = new ServiceConnection() {
    523         public void onServiceConnected(ComponentName className, IBinder service) {
    524             if (DBG) Log.d(TAG, "Proxy object connected");
    525             mService = IBluetoothA2dp.Stub.asInterface(service);
    526 
    527             if (mServiceListener != null) {
    528                 mServiceListener.onServiceConnected(BluetoothProfile.A2DP, BluetoothA2dp.this);
    529             }
    530         }
    531         public void onServiceDisconnected(ComponentName className) {
    532             if (DBG) Log.d(TAG, "Proxy object disconnected");
    533             mService = null;
    534             if (mServiceListener != null) {
    535                 mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP);
    536             }
    537         }
    538     };
    539 
    540     private boolean isEnabled() {
    541        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
    542        return false;
    543     }
    544 
    545     private boolean isValidDevice(BluetoothDevice device) {
    546        if (device == null) return false;
    547 
    548        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
    549        return false;
    550     }
    551 
    552     private static void log(String msg) {
    553       Log.d(TAG, msg);
    554     }
    555 }
    556