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