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