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