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