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.media.MediaMetadata;
     24 import android.media.session.PlaybackState;
     25 import android.os.IBinder;
     26 import android.os.RemoteException;
     27 import android.util.Log;
     28 
     29 import java.util.ArrayList;
     30 import java.util.List;
     31 
     32 /**
     33  * This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently
     34  * supports player information, playback support and track metadata.
     35  *
     36  *<p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP
     37  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
     38  * the BluetoothAvrcpController proxy object.
     39  *
     40  * {@hide}
     41  */
     42 public final class BluetoothAvrcpController implements BluetoothProfile {
     43     private static final String TAG = "BluetoothAvrcpController";
     44     private static final boolean DBG = false;
     45     private static final boolean VDBG = false;
     46 
     47     /**
     48      * Intent used to broadcast the change in connection state of the AVRCP Controller
     49      * profile.
     50      *
     51      * <p>This intent will have 3 extras:
     52      * <ul>
     53      *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
     54      *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
     55      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
     56      * </ul>
     57      *
     58      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
     59      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
     60      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
     61      *
     62      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
     63      * receive.
     64      */
     65     public static final String ACTION_CONNECTION_STATE_CHANGED =
     66         "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED";
     67 
     68     /**
     69      * Intent used to broadcast the change in metadata state of playing track on the AVRCP
     70      * AG.
     71      *
     72      * <p>This intent will have the two extras:
     73      * <ul>
     74      *    <li> {@link #EXTRA_METADATA} - {@link MediaMetadata} containing the current metadata.</li>
     75      *    <li> {@link #EXTRA_PLAYBACK} - {@link PlaybackState} containing the current playback
     76      *    state. </li>
     77      * </ul>
     78      */
     79     public static final String ACTION_TRACK_EVENT =
     80         "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT";
     81 
     82 
     83     /**
     84      * Intent used to broadcast the change in player application setting state on AVRCP AG.
     85      *
     86      * <p>This intent will have the following extras:
     87      * <ul>
     88      *    <li> {@link #EXTRA_PLAYER_SETTING} - {@link BluetoothAvrcpPlayerSettings} containing the
     89      *    most recent player setting. </li>
     90      * </ul>
     91      */
     92     public static final String ACTION_PLAYER_SETTING =
     93         "android.bluetooth.avrcp-controller.profile.action.PLAYER_SETTING";
     94 
     95     public static final String EXTRA_METADATA =
     96             "android.bluetooth.avrcp-controller.profile.extra.METADATA";
     97 
     98     public static final String EXTRA_PLAYBACK =
     99             "android.bluetooth.avrcp-controller.profile.extra.PLAYBACK";
    100 
    101     public static final String EXTRA_PLAYER_SETTING =
    102             "android.bluetooth.avrcp-controller.profile.extra.PLAYER_SETTING";
    103 
    104     /*
    105      * KeyCoded for Pass Through Commands
    106      */
    107     public static final int PASS_THRU_CMD_ID_PLAY = 0x44;
    108     public static final int PASS_THRU_CMD_ID_PAUSE = 0x46;
    109     public static final int PASS_THRU_CMD_ID_VOL_UP = 0x41;
    110     public static final int PASS_THRU_CMD_ID_VOL_DOWN = 0x42;
    111     public static final int PASS_THRU_CMD_ID_STOP = 0x45;
    112     public static final int PASS_THRU_CMD_ID_FF = 0x49;
    113     public static final int PASS_THRU_CMD_ID_REWIND = 0x48;
    114     public static final int PASS_THRU_CMD_ID_FORWARD = 0x4B;
    115     public static final int PASS_THRU_CMD_ID_BACKWARD = 0x4C;
    116     /* Key State Variables */
    117     public static final int KEY_STATE_PRESSED = 0;
    118     public static final int KEY_STATE_RELEASED = 1;
    119     /* Group Navigation Key Codes */
    120     public static final int PASS_THRU_CMD_ID_NEXT_GRP = 0x00;
    121     public static final int PASS_THRU_CMD_ID_PREV_GRP = 0x01;
    122 
    123 
    124     private Context mContext;
    125     private ServiceListener mServiceListener;
    126     private IBluetoothAvrcpController mService;
    127     private BluetoothAdapter mAdapter;
    128 
    129     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
    130         new IBluetoothStateChangeCallback.Stub() {
    131             public void onBluetoothStateChange(boolean up) {
    132                 if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
    133                 if (!up) {
    134                     if (VDBG) Log.d(TAG,"Unbinding service...");
    135                     synchronized (mConnection) {
    136                         try {
    137                             mService = null;
    138                             mContext.unbindService(mConnection);
    139                         } catch (Exception re) {
    140                             Log.e(TAG,"",re);
    141                         }
    142                     }
    143                 } else {
    144                     synchronized (mConnection) {
    145                         try {
    146                             if (mService == null) {
    147                                 if (VDBG) Log.d(TAG,"Binding service...");
    148                                 doBind();
    149                             }
    150                         } catch (Exception re) {
    151                             Log.e(TAG,"",re);
    152                         }
    153                     }
    154                 }
    155             }
    156       };
    157 
    158     /**
    159      * Create a BluetoothAvrcpController proxy object for interacting with the local
    160      * Bluetooth AVRCP service.
    161      *
    162      */
    163     /*package*/ BluetoothAvrcpController(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(IBluetoothAvrcpController.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 AVRCP Controller 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     /**
    219      * {@inheritDoc}
    220      */
    221     public List<BluetoothDevice> getConnectedDevices() {
    222         if (VDBG) log("getConnectedDevices()");
    223         if (mService != null && isEnabled()) {
    224             try {
    225                 return mService.getConnectedDevices();
    226             } catch (RemoteException e) {
    227                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    228                 return new ArrayList<BluetoothDevice>();
    229             }
    230         }
    231         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    232         return new ArrayList<BluetoothDevice>();
    233     }
    234 
    235     /**
    236      * {@inheritDoc}
    237      */
    238     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    239         if (VDBG) log("getDevicesMatchingStates()");
    240         if (mService != null && isEnabled()) {
    241             try {
    242                 return mService.getDevicesMatchingConnectionStates(states);
    243             } catch (RemoteException e) {
    244                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    245                 return new ArrayList<BluetoothDevice>();
    246             }
    247         }
    248         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    249         return new ArrayList<BluetoothDevice>();
    250     }
    251 
    252     /**
    253      * {@inheritDoc}
    254      */
    255     public int getConnectionState(BluetoothDevice device) {
    256         if (VDBG) log("getState(" + device + ")");
    257         if (mService != null && isEnabled()
    258             && isValidDevice(device)) {
    259             try {
    260                 return mService.getConnectionState(device);
    261             } catch (RemoteException e) {
    262                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    263                 return BluetoothProfile.STATE_DISCONNECTED;
    264             }
    265         }
    266         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    267         return BluetoothProfile.STATE_DISCONNECTED;
    268     }
    269 
    270     public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
    271         if (DBG) Log.d(TAG, "sendPassThroughCmd");
    272         if (mService != null && isEnabled()) {
    273             try {
    274                 mService.sendPassThroughCmd(device, keyCode, keyState);
    275                 return;
    276             } catch (RemoteException e) {
    277                 Log.e(TAG, "Error talking to BT service in sendPassThroughCmd()", e);
    278                 return;
    279             }
    280         }
    281         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    282     }
    283 
    284     /**
    285      * Gets the player application settings.
    286      *
    287      * @return the {@link BluetoothAvrcpPlayerSettings} or {@link null} if there is an error.
    288      */
    289     public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
    290         if (DBG) Log.d(TAG, "getPlayerSettings");
    291         BluetoothAvrcpPlayerSettings settings = null;
    292         if (mService != null && isEnabled()) {
    293             try {
    294                 settings = mService.getPlayerSettings(device);
    295             } catch (RemoteException e) {
    296                 Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
    297                 return null;
    298             }
    299         }
    300         return settings;
    301     }
    302 
    303     /**
    304      * Gets the metadata for the current track.
    305      *
    306      * This should be usually called when application UI needs to be updated, eg. when the track
    307      * changes or immediately after connecting and getting the current state.
    308      * @return the {@link MediaMetadata} or {@link null} if there is an error.
    309      */
    310     public MediaMetadata getMetadata(BluetoothDevice device) {
    311         if (DBG) Log.d(TAG, "getMetadata");
    312         MediaMetadata metadata = null;
    313         if (mService != null && isEnabled()) {
    314             try {
    315                 metadata = mService.getMetadata(device);
    316             } catch (RemoteException e) {
    317                 Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
    318                 return null;
    319             }
    320         }
    321         return metadata;
    322     }
    323 
    324     /**
    325      * Gets the playback state for current track.
    326      *
    327      * When the application is first connecting it can use current track state to get playback info.
    328      * For all further updates it should listen to notifications.
    329      * @return the {@link PlaybackState} or {@link null} if there is an error.
    330      */
    331     public PlaybackState getPlaybackState(BluetoothDevice device) {
    332         if (DBG) Log.d(TAG, "getPlaybackState");
    333         PlaybackState playbackState = null;
    334         if (mService != null && isEnabled()) {
    335             try {
    336                 playbackState = mService.getPlaybackState(device);
    337             } catch (RemoteException e) {
    338                 Log.e(TAG,
    339                     "Error talking to BT service in getPlaybackState() " + e);
    340                 return null;
    341             }
    342         }
    343         return playbackState;
    344     }
    345 
    346     /**
    347      * Sets the player app setting for current player.
    348      * returns true in case setting is supported by remote, false otherwise
    349      */
    350     public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
    351         if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
    352         if (mService != null && isEnabled()) {
    353             try {
    354                 return mService.setPlayerApplicationSetting(plAppSetting);
    355             } catch (RemoteException e) {
    356                 Log.e(TAG, "Error talking to BT service in setPlayerApplicationSetting() " + e);
    357                 return false;
    358             }
    359         }
    360         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    361         return false;
    362     }
    363 
    364     /*
    365      * Send Group Navigation Command to Remote.
    366      * possible keycode values: next_grp, previous_grp defined above
    367      */
    368     public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
    369         Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = " + keyState);
    370         if (mService != null && isEnabled()) {
    371             try {
    372                 mService.sendGroupNavigationCmd(device, keyCode, keyState);
    373                 return;
    374             } catch (RemoteException e) {
    375                 Log.e(TAG, "Error talking to BT service in sendGroupNavigationCmd()", e);
    376                 return;
    377             }
    378         }
    379         if (mService == null) Log.w(TAG, "Proxy not attached to service");
    380     }
    381 
    382     private final ServiceConnection mConnection = new ServiceConnection() {
    383         public void onServiceConnected(ComponentName className, IBinder service) {
    384             if (DBG) Log.d(TAG, "Proxy object connected");
    385             mService = IBluetoothAvrcpController.Stub.asInterface(service);
    386 
    387             if (mServiceListener != null) {
    388                 mServiceListener.onServiceConnected(BluetoothProfile.AVRCP_CONTROLLER,
    389                         BluetoothAvrcpController.this);
    390             }
    391         }
    392         public void onServiceDisconnected(ComponentName className) {
    393             if (DBG) Log.d(TAG, "Proxy object disconnected");
    394             mService = null;
    395             if (mServiceListener != null) {
    396                 mServiceListener.onServiceDisconnected(BluetoothProfile.AVRCP_CONTROLLER);
    397             }
    398         }
    399     };
    400 
    401     private boolean isEnabled() {
    402        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
    403        return false;
    404     }
    405 
    406     private boolean isValidDevice(BluetoothDevice device) {
    407        if (device == null) return false;
    408 
    409        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
    410        return false;
    411     }
    412 
    413     private static void log(String msg) {
    414       Log.d(TAG, msg);
    415     }
    416 }
    417