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