AvrcpControllerService.java revision ea14e69d8b1c7293869c0e4ae9180f36149a95b7
1/* 2 * Copyright (C) 2016 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 17package com.android.bluetooth.avrcpcontroller; 18 19import android.bluetooth.BluetoothAdapter; 20import android.bluetooth.BluetoothAvrcpPlayerSettings; 21import android.bluetooth.BluetoothDevice; 22import android.bluetooth.BluetoothProfile; 23import android.bluetooth.IBluetoothAvrcpController; 24import android.content.Context; 25import android.media.AudioManager; 26import android.media.browse.MediaBrowser; 27import android.media.browse.MediaBrowser.MediaItem; 28import android.media.MediaDescription; 29import android.media.MediaMetadata; 30import android.media.session.PlaybackState; 31import android.os.Bundle; 32import android.os.HandlerThread; 33import android.os.Looper; 34import android.os.Message; 35import android.util.Log; 36 37import com.android.bluetooth.Utils; 38import com.android.bluetooth.btservice.ProfileService; 39 40import java.util.ArrayList; 41import java.util.Arrays; 42import java.util.HashMap; 43import java.util.List; 44import java.util.Map; 45import java.util.UUID; 46 47/** 48 * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application. 49 */ 50public class AvrcpControllerService extends ProfileService { 51 static final String TAG = "AvrcpControllerService"; 52 static final boolean DBG = true; 53 static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); 54 /* 55 * Play State Values from JNI 56 */ 57 private static final byte JNI_PLAY_STATUS_STOPPED = 0x00; 58 private static final byte JNI_PLAY_STATUS_PLAYING = 0x01; 59 private static final byte JNI_PLAY_STATUS_PAUSED = 0x02; 60 private static final byte JNI_PLAY_STATUS_FWD_SEEK = 0x03; 61 private static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04; 62 private static final byte JNI_PLAY_STATUS_ERROR = -1; 63 64 /* 65 * Browsing Media Item Attribute IDs 66 * This should be kept in sync with BTRC_MEDIA_ATTR_ID_* in bt_rc.h 67 */ 68 private static final int JNI_MEDIA_ATTR_ID_INVALID = -1; 69 private static final int JNI_MEDIA_ATTR_ID_TITLE = 0x00000001; 70 private static final int JNI_MEDIA_ATTR_ID_ARTIST = 0x00000002; 71 private static final int JNI_MEDIA_ATTR_ID_ALBUM = 0x00000003; 72 private static final int JNI_MEDIA_ATTR_ID_TRACK_NUM = 0x00000004; 73 private static final int JNI_MEDIA_ATTR_ID_NUM_TRACKS = 0x00000005; 74 private static final int JNI_MEDIA_ATTR_ID_GENRE = 0x00000006; 75 private static final int JNI_MEDIA_ATTR_ID_PLAYING_TIME = 0x00000007; 76 77 /* 78 * Browsing folder types 79 * This should be kept in sync with BTRC_FOLDER_TYPE_* in bt_rc.h 80 */ 81 private static final int JNI_FOLDER_TYPE_TITLES = 0x01; 82 private static final int JNI_FOLDER_TYPE_ALBUMS = 0x02; 83 private static final int JNI_FOLDER_TYPE_ARTISTS = 0x03; 84 private static final int JNI_FOLDER_TYPE_GENRES = 0x04; 85 private static final int JNI_FOLDER_TYPE_PLAYLISTS = 0x05; 86 private static final int JNI_FOLDER_TYPE_YEARS = 0x06; 87 88 /* 89 * AVRCP Error types as defined in spec. Also they should be in sync with btrc_status_t. 90 * NOTE: Not all may be defined. 91 */ 92 private static final int JNI_AVRC_STS_NO_ERROR = 0x04; 93 private static final int JNI_AVRC_INV_RANGE = 0x0b; 94 95 /** 96 * Intent used to broadcast the change in browse connection state of the AVRCP Controller 97 * profile. 98 * 99 * <p>This intent will have 2 extras: 100 * <ul> 101 * <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li> 102 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 103 * </ul> 104 * 105 * <p>{@link #EXTRA_STATE} can be any of 106 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, 107 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. 108 * 109 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 110 * receive. 111 */ 112 public static final String ACTION_BROWSE_CONNECTION_STATE_CHANGED = 113 "android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED"; 114 115 /** 116 * intent used to broadcast the change in metadata state of playing track on the avrcp 117 * ag. 118 * 119 * <p>this intent will have the two extras: 120 * <ul> 121 * <li> {@link #extra_metadata} - {@link mediametadata} containing the current metadata.</li> 122 * <li> {@link #extra_playback} - {@link playbackstate} containing the current playback 123 * state. </li> 124 * </ul> 125 */ 126 public static final String ACTION_TRACK_EVENT = 127 "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT"; 128 129 /** 130 * Intent used to broadcast the change of folder list. 131 * 132 * <p>This intent will have the one extra: 133 * <ul> 134 * <li> {@link #EXTRA_FOLDER_LIST} - array of {@link MediaBrowser#MediaItem} 135 * containing the folder listing of currently selected folder. 136 * </ul> 137 */ 138 public static final String ACTION_FOLDER_LIST = 139 "android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST"; 140 141 public static final String EXTRA_FOLDER_LIST = 142 "android.bluetooth.avrcp-controller.profile.extra.FOLDER_LIST"; 143 144 public static final String EXTRA_FOLDER_ID = "com.android.bluetooth.avrcp.EXTRA_FOLDER_ID"; 145 public static final String EXTRA_FOLDER_BT_ID = 146 "com.android.bluetooth.avrcp-controller.EXTRA_FOLDER_BT_ID"; 147 148 public static final String EXTRA_METADATA = 149 "android.bluetooth.avrcp-controller.profile.extra.METADATA"; 150 151 public static final String EXTRA_PLAYBACK = 152 "android.bluetooth.avrcp-controller.profile.extra.PLAYBACK"; 153 154 public static final String MEDIA_ITEM_UID_KEY = "media-item-uid-key"; 155 156 /* 157 * KeyCoded for Pass Through Commands 158 */ 159 public static final int PASS_THRU_CMD_ID_PLAY = 0x44; 160 public static final int PASS_THRU_CMD_ID_PAUSE = 0x46; 161 public static final int PASS_THRU_CMD_ID_VOL_UP = 0x41; 162 public static final int PASS_THRU_CMD_ID_VOL_DOWN = 0x42; 163 public static final int PASS_THRU_CMD_ID_STOP = 0x45; 164 public static final int PASS_THRU_CMD_ID_FF = 0x49; 165 public static final int PASS_THRU_CMD_ID_REWIND = 0x48; 166 public static final int PASS_THRU_CMD_ID_FORWARD = 0x4B; 167 public static final int PASS_THRU_CMD_ID_BACKWARD = 0x4C; 168 169 /* Key State Variables */ 170 public static final int KEY_STATE_PRESSED = 0; 171 public static final int KEY_STATE_RELEASED = 1; 172 173 /* Group Navigation Key Codes */ 174 public static final int PASS_THRU_CMD_ID_NEXT_GRP = 0x00; 175 public static final int PASS_THRU_CMD_ID_PREV_GRP = 0x01; 176 177 /* Folder navigation directions 178 * This is borrowed from AVRCP 1.6 spec and must be kept with same values 179 */ 180 public static final int FOLDER_NAVIGATION_DIRECTION_UP = 0x00; 181 public static final int FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01; 182 183 /* Folder/Media Item scopes. 184 * Keep in sync with AVRCP 1.6 sec. 6.10.1 185 */ 186 public static final int BROWSE_SCOPE_PLAYER_LIST = 0x00; 187 public static final int BROWSE_SCOPE_VFS = 0x01; 188 public static final int BROWSE_SCOPE_SEARCH = 0x02; 189 public static final int BROWSE_SCOPE_NOW_PLAYING = 0x03; 190 191 private AvrcpControllerStateMachine mAvrcpCtSm; 192 private static AvrcpControllerService sAvrcpControllerService; 193 // UID size is 8 bytes (AVRCP 1.6 spec) 194 private static final byte[] EMPTY_UID = {0, 0, 0, 0, 0, 0, 0, 0}; 195 196 // We only support one device. 197 private BluetoothDevice mConnectedDevice = null; 198 // If browse is supported (only valid if mConnectedDevice != null). 199 private boolean mBrowseConnected = false; 200 // Caches the current browse folder. If this is null then root is the currently browsed folder 201 // (which also has no UID). 202 private String mCurrentBrowseFolderUID = null; 203 204 static { 205 classInitNative(); 206 } 207 208 public AvrcpControllerService() { 209 initNative(); 210 } 211 212 protected String getName() { 213 return TAG; 214 } 215 216 protected IProfileServiceBinder initBinder() { 217 return new BluetoothAvrcpControllerBinder(this); 218 } 219 220 protected boolean start() { 221 HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler"); 222 thread.start(); 223 mAvrcpCtSm = new AvrcpControllerStateMachine(this); 224 mAvrcpCtSm.start(); 225 226 setAvrcpControllerService(this); 227 return true; 228 } 229 230 protected boolean stop() { 231 if (mAvrcpCtSm != null) { 232 mAvrcpCtSm.doQuit(); 233 } 234 return true; 235 } 236 237 //API Methods 238 239 public static synchronized AvrcpControllerService getAvrcpControllerService() { 240 if (sAvrcpControllerService != null && sAvrcpControllerService.isAvailable()) { 241 if (DBG) { 242 Log.d(TAG, "getAvrcpControllerService(): returning " 243 + sAvrcpControllerService); 244 } 245 return sAvrcpControllerService; 246 } 247 if (DBG) { 248 if (sAvrcpControllerService == null) { 249 Log.d(TAG, "getAvrcpControllerService(): service is NULL"); 250 } else if (!(sAvrcpControllerService.isAvailable())) { 251 Log.d(TAG, "getAvrcpControllerService(): service is not available"); 252 } 253 } 254 return null; 255 } 256 257 private static synchronized void setAvrcpControllerService(AvrcpControllerService instance) { 258 if (instance != null && instance.isAvailable()) { 259 if (DBG) { 260 Log.d(TAG, "setAvrcpControllerService(): set to: " + sAvrcpControllerService); 261 } 262 sAvrcpControllerService = instance; 263 } else { 264 if (DBG) { 265 if (instance == null) { 266 Log.d(TAG, "setAvrcpControllerService(): service not available"); 267 } else if (!instance.isAvailable()) { 268 Log.d(TAG, "setAvrcpControllerService(): service is cleaning up"); 269 } 270 } 271 } 272 } 273 274 private static synchronized void clearAvrcpControllerService() { 275 sAvrcpControllerService = null; 276 } 277 278 public synchronized List<BluetoothDevice> getConnectedDevices() { 279 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 280 List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); 281 if (mConnectedDevice != null) { 282 devices.add(mConnectedDevice); 283 } 284 return devices; 285 } 286 287 /** 288 * This function only supports STATE_CONNECTED 289 */ 290 public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 291 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 292 List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); 293 for (int i = 0; i < states.length; i++) { 294 if (states[i] == BluetoothProfile.STATE_CONNECTED && mConnectedDevice != null) { 295 devices.add(mConnectedDevice); 296 } 297 } 298 return devices; 299 } 300 301 public synchronized int getConnectionState(BluetoothDevice device) { 302 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 303 return (mConnectedDevice != null ? BluetoothProfile.STATE_CONNECTED : 304 BluetoothProfile.STATE_DISCONNECTED); 305 } 306 307 public synchronized void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) { 308 Log.v(TAG, "sendGroupNavigationCmd keyCode: " + keyCode + " keyState: " + keyState); 309 if (device == null) { 310 Log.e(TAG, "sendGroupNavigationCmd device is null"); 311 } 312 313 if (!(device.equals(mConnectedDevice))) { 314 Log.e(TAG, " Device does not match " + device + " connected " + mConnectedDevice); 315 return; 316 } 317 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 318 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 319 MESSAGE_SEND_GROUP_NAVIGATION_CMD, keyCode, keyState, device); 320 mAvrcpCtSm.sendMessage(msg); 321 } 322 323 public synchronized void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) { 324 Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState); 325 if (device == null) { 326 Log.e(TAG, "sendPassThroughCmd Device is null"); 327 return; 328 } 329 330 if (!device.equals(mConnectedDevice)) { 331 Log.w(TAG, " Device does not match device " + device + " conn " + mConnectedDevice); 332 return; 333 } 334 335 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 336 Message msg = mAvrcpCtSm 337 .obtainMessage(AvrcpControllerStateMachine.MESSAGE_SEND_PASS_THROUGH_CMD, 338 keyCode, keyState, device); 339 mAvrcpCtSm.sendMessage(msg); 340 } 341 342 public void startAvrcpUpdates() { 343 mAvrcpCtSm.obtainMessage( 344 AvrcpControllerStateMachine.MESSAGE_START_METADATA_BROADCASTS).sendToTarget(); 345 } 346 347 public void stopAvrcpUpdates() { 348 mAvrcpCtSm.obtainMessage( 349 AvrcpControllerStateMachine.MESSAGE_STOP_METADATA_BROADCASTS).sendToTarget(); 350 } 351 352 public synchronized MediaMetadata getMetaData(BluetoothDevice device) { 353 if (DBG) { 354 Log.d(TAG, "getMetaData"); 355 } 356 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 357 if (device == null) { 358 Log.e(TAG, "getMetadata device is null"); 359 return null; 360 } 361 362 if (!device.equals(mConnectedDevice)) { 363 return null; 364 } 365 return mAvrcpCtSm.getCurrentMetaData(); 366 } 367 368 public PlaybackState getPlaybackState(BluetoothDevice device) { 369 // Get the cached state by default. 370 return getPlaybackState(device, true); 371 } 372 373 // cached can be used to force a getPlaybackState command. Useful for PTS testing. 374 public synchronized PlaybackState getPlaybackState(BluetoothDevice device, boolean cached) { 375 if (DBG) { 376 Log.d(TAG, "getPlayBackState device = " + device); 377 } 378 379 if (device == null) { 380 Log.e(TAG, "getPlaybackState device is null"); 381 return null; 382 } 383 384 if (!device.equals(mConnectedDevice)) { 385 Log.e(TAG, "Device " + device + " does not match connected deivce " + mConnectedDevice); 386 return null; 387 388 } 389 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 390 return mAvrcpCtSm.getCurrentPlayBackState(cached); 391 } 392 393 public synchronized BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) { 394 if (DBG) { 395 Log.d(TAG, "getPlayerApplicationSetting "); 396 } 397 398 if (device == null) { 399 Log.e(TAG, "getPlayerSettings device is null"); 400 return null; 401 } 402 403 if (!device.equals(mConnectedDevice)) { 404 Log.e(TAG, "device " + device + " does not match connected device " + mConnectedDevice); 405 return null; 406 } 407 408 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 409 410 /* Do nothing */ 411 return null; 412 } 413 414 public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) { 415 if (DBG) { 416 Log.d(TAG, "getPlayerApplicationSetting"); 417 } 418 419 /* Do nothing */ 420 return false; 421 } 422 423 /** 424 * Fetches the list of children for the parentID node. 425 * 426 * This function manages the overall tree for browsing structure. 427 * 428 * Arguments: 429 * device - Device to browse content for. 430 * parentMediaId - ID of the parent that we need to browse content for. Since most 431 * of the players are database unware, fetching a root invalidates all the children. 432 * start - number of item to start scanning from 433 * items - number of items to fetch 434 */ 435 public synchronized void getChildren(BluetoothDevice device, String parentMediaId, int start, int items) { 436 if (DBG) { 437 Log.d(TAG, "getChildrent device = " + device + " parent " + parentMediaId); 438 } 439 440 if (device == null) { 441 Log.e(TAG, "getChildren device is null"); 442 return; 443 } 444 445 if (!device.equals(mConnectedDevice)) { 446 Log.e(TAG, "getChildren device " + device + " does not match " + 447 mConnectedDevice); 448 return; 449 } 450 451 if (!mBrowseConnected) { 452 Log.e(TAG, "getChildren browse not yet connected"); 453 return; 454 } 455 456 if (!mAvrcpCtSm.isConnected()) { 457 return; 458 } 459 mAvrcpCtSm.getChildren(parentMediaId, start, items); 460 } 461 462 public synchronized void getNowPlayingList(BluetoothDevice device, String id, int start, int items) { 463 if (DBG) { 464 Log.d(TAG, "getNowPlayingList device = " + device + " start = " + start + 465 "items = " + items); 466 } 467 468 if (device == null) { 469 Log.e(TAG, "getNowPlayingList device is null"); 470 return; 471 } 472 473 if (!device.equals(mConnectedDevice)) { 474 Log.e(TAG, "getNowPlayingList device " + device + " does not match " + mConnectedDevice); 475 return; 476 } 477 478 if (!mBrowseConnected) { 479 Log.e(TAG, "getNowPlayingList browse not yet connected"); 480 } 481 482 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 483 484 Message msg = mAvrcpCtSm.obtainMessage( 485 AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST, start, items, id); 486 mAvrcpCtSm.sendMessage(msg); 487 } 488 489 public synchronized void getFolderList(BluetoothDevice device, String id, int start, int items) { 490 if (DBG) { 491 Log.d(TAG, "getFolderListing device = " + device + " start = " + start + 492 "items = " + items); 493 } 494 495 if (device == null) { 496 Log.e(TAG, "getFolderListing device is null"); 497 return; 498 } 499 500 if (!device.equals(mConnectedDevice)) { 501 Log.e(TAG, "getFolderListing device " + device + " does not match " + mConnectedDevice); 502 return; 503 } 504 505 if (!mBrowseConnected) { 506 Log.e(TAG, "getFolderListing browse not yet connected"); 507 return; 508 } 509 510 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 511 512 Message msg = mAvrcpCtSm.obtainMessage( 513 AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start, items, id); 514 mAvrcpCtSm.sendMessage(msg); 515 } 516 517 public synchronized void getPlayerList(BluetoothDevice device, int start, int items) { 518 if (DBG) { 519 Log.d(TAG, "getPlayerList device = " + device + " start = " + start + 520 "items = " + items); 521 } 522 523 if (device == null) { 524 Log.e(TAG, "getPlayerList device is null"); 525 return; 526 } 527 528 if (!device.equals(mConnectedDevice)) { 529 Log.e(TAG, "getPlayerList device " + device + " does not match " + mConnectedDevice); 530 return; 531 } 532 533 if (!mBrowseConnected) { 534 Log.e(TAG, "getPlayerList browse not yet connected"); 535 return; 536 } 537 538 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 539 540 Message msg = mAvrcpCtSm.obtainMessage( 541 AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items); 542 mAvrcpCtSm.sendMessage(msg); 543 } 544 545 public synchronized void changeFolderPath( 546 BluetoothDevice device, int direction, String uid, String fid) { 547 if (DBG) { 548 Log.d(TAG, "changeFolderPath device = " + device + " direction " + 549 direction + " uid " + uid); 550 } 551 552 if (device == null) { 553 Log.e(TAG, "changeFolderPath device is null"); 554 return; 555 } 556 557 if (!device.equals(mConnectedDevice)) { 558 Log.e(TAG, "changeFolderPath device " + device + " does not match " + 559 mConnectedDevice); 560 return; 561 } 562 563 if (!mBrowseConnected) { 564 Log.e(TAG, "changeFolderPath browse not yet connected"); 565 return; 566 } 567 568 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 569 570 Bundle b = new Bundle(); 571 b.putString(EXTRA_FOLDER_ID, fid); 572 b.putString(EXTRA_FOLDER_BT_ID, uid); 573 Message msg = mAvrcpCtSm.obtainMessage( 574 AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH, direction, 0, b); 575 mAvrcpCtSm.sendMessage(msg); 576 } 577 578 public void setBrowsedPlayer(BluetoothDevice device, int id, String fid) { 579 if (DBG) { 580 Log.d(TAG, "setBrowsedPlayer device = " + device + " id" + id + " fid " + fid); 581 } 582 583 if (device == null) { 584 Log.e(TAG, "setBrowsedPlayer device is null"); 585 return; 586 } 587 588 589 if (!mBrowseConnected) { 590 Log.e(TAG, "setBrowsedPlayer browse not yet connected"); 591 return; 592 } 593 594 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 595 596 Message msg = mAvrcpCtSm.obtainMessage( 597 AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, id, 0, fid); 598 mAvrcpCtSm.sendMessage(msg); 599 } 600 601 public synchronized void fetchAttrAndPlayItem(BluetoothDevice device, String uid) { 602 if (DBG) { 603 Log.d(TAG, "fetchAttrAndPlayItem device = " + device + " uid " + uid); 604 } 605 606 if (device == null) { 607 Log.e(TAG, "fetchAttrAndPlayItem device is null"); 608 return; 609 } 610 611 if (!device.equals(mConnectedDevice)) { 612 Log.e(TAG, "fetchAttrAndPlayItem device " + device + " does not match " + 613 mConnectedDevice); 614 return; 615 } 616 617 if (!mBrowseConnected) { 618 Log.e(TAG, "fetchAttrAndPlayItem browse not yet connected"); 619 return; 620 } 621 mAvrcpCtSm.fetchAttrAndPlayItem(uid); 622 } 623 624 //Binder object: Must be static class or memory leak may occur 625 private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub 626 implements IProfileServiceBinder { 627 628 private AvrcpControllerService mService; 629 630 private AvrcpControllerService getService() { 631 if (!Utils.checkCaller()) { 632 Log.w(TAG, "AVRCP call not allowed for non-active user"); 633 return null; 634 } 635 636 if (mService != null && mService.isAvailable()) { 637 return mService; 638 } 639 return null; 640 } 641 642 BluetoothAvrcpControllerBinder(AvrcpControllerService svc) { 643 mService = svc; 644 } 645 646 public boolean cleanup() { 647 mService = null; 648 return true; 649 } 650 651 @Override 652 public List<BluetoothDevice> getConnectedDevices() { 653 AvrcpControllerService service = getService(); 654 if (service == null) { 655 return new ArrayList<BluetoothDevice>(0); 656 } 657 return service.getConnectedDevices(); 658 } 659 660 @Override 661 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 662 AvrcpControllerService service = getService(); 663 if (service == null) { 664 return new ArrayList<BluetoothDevice>(0); 665 } 666 return service.getDevicesMatchingConnectionStates(states); 667 } 668 669 @Override 670 public int getConnectionState(BluetoothDevice device) { 671 AvrcpControllerService service = getService(); 672 if (service == null) { 673 return BluetoothProfile.STATE_DISCONNECTED; 674 } 675 676 if (device == null) { 677 throw new IllegalStateException("Device cannot be null!"); 678 } 679 680 return service.getConnectionState(device); 681 } 682 683 @Override 684 public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) { 685 Log.v(TAG, "Binder Call: sendGroupNavigationCmd"); 686 AvrcpControllerService service = getService(); 687 if (service == null) { 688 return; 689 } 690 691 if (device == null) { 692 throw new IllegalStateException("Device cannot be null!"); 693 } 694 695 service.sendGroupNavigationCmd(device, keyCode, keyState); 696 } 697 698 @Override 699 public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) { 700 Log.v(TAG, "Binder Call: getPlayerApplicationSetting "); 701 AvrcpControllerService service = getService(); 702 if (service == null) { 703 return null; 704 } 705 706 if (device == null) { 707 throw new IllegalStateException("Device cannot be null!"); 708 } 709 710 return service.getPlayerSettings(device); 711 } 712 713 @Override 714 public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) { 715 Log.v(TAG, "Binder Call: setPlayerApplicationSetting "); 716 AvrcpControllerService service = getService(); 717 if (service == null) { 718 return false; 719 } 720 return service.setPlayerApplicationSetting(plAppSetting); 721 } 722 } 723 724 // Called by JNI when a passthrough key was received. 725 private void handlePassthroughRsp(int id, int keyState, byte[] address) { 726 Log.d(TAG, "passthrough response received as: key: " + id + " state: " + keyState + 727 "address:" + address); 728 } 729 730 private void handleGroupNavigationRsp(int id, int keyState) { 731 Log.d(TAG, "group navigation response received as: key: " + id + " state: " + keyState); 732 } 733 734 // Called by JNI when a device has connected or disconnected. 735 private synchronized void onConnectionStateChanged( 736 boolean rc_connected, boolean br_connected, byte[] address) { 737 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 738 Log.d(TAG, "onConnectionStateChanged " + rc_connected + " " + br_connected + 739 device + " conn device " + mConnectedDevice); 740 if (device == null) { 741 Log.e(TAG, "onConnectionStateChanged Device is null"); 742 return; 743 } 744 745 // Adjust the AVRCP connection state. 746 int oldState = (device.equals(mConnectedDevice) ? BluetoothProfile.STATE_CONNECTED : 747 BluetoothProfile.STATE_DISCONNECTED); 748 int newState = (rc_connected ? BluetoothProfile.STATE_CONNECTED : 749 BluetoothProfile.STATE_DISCONNECTED); 750 751 if (rc_connected && oldState == BluetoothProfile.STATE_DISCONNECTED) { 752 /* AVRCPControllerService supports single connection */ 753 if (mConnectedDevice != null) { 754 Log.d(TAG, "A Connection already exists, returning"); 755 return; 756 } 757 mConnectedDevice = device; 758 Message msg = mAvrcpCtSm.obtainMessage( 759 AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState, 760 oldState, device); 761 mAvrcpCtSm.sendMessage(msg); 762 } else if (!rc_connected && oldState == BluetoothProfile.STATE_CONNECTED) { 763 mConnectedDevice = null; 764 Message msg = mAvrcpCtSm.obtainMessage( 765 AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState, 766 oldState, device); 767 mAvrcpCtSm.sendMessage(msg); 768 } 769 770 // Adjust the browse connection state. If RC is connected we should have already sent the 771 // connection status out. 772 if (rc_connected && br_connected) { 773 mBrowseConnected = true; 774 Message msg = mAvrcpCtSm.obtainMessage( 775 AvrcpControllerStateMachine.MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE); 776 msg.arg1 = 1; 777 msg.obj = device; 778 mAvrcpCtSm.sendMessage(msg); 779 } 780 } 781 782 // Called by JNI to notify Avrcp of features supported by the Remote device. 783 private void getRcFeatures(byte[] address, int features) { 784 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 785 Message msg = mAvrcpCtSm.obtainMessage( 786 AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES, features, 0, device); 787 mAvrcpCtSm.sendMessage(msg); 788 } 789 790 // Called by JNI 791 private void setPlayerAppSettingRsp(byte[] address, byte accepted) { 792 /* Do Nothing. */ 793 } 794 795 // Called by JNI when remote wants to receive absolute volume notifications. 796 private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) { 797 Log.d(TAG, "handleRegisterNotificationAbsVol "); 798 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 799 if (device != null && !device.equals(mConnectedDevice)) { 800 Log.e(TAG, "handleRegisterNotificationAbsVol device not found " + address); 801 return; 802 } 803 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 804 MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION, (int) label, 0); 805 mAvrcpCtSm.sendMessage(msg); 806 } 807 808 // Called by JNI when remote wants to set absolute volume. 809 private synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) { 810 Log.d(TAG, "handleSetAbsVolume "); 811 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 812 if (device != null && !device.equals(mConnectedDevice)) { 813 Log.e(TAG, "handleSetAbsVolume device not found " + address); 814 return; 815 } 816 Message msg = mAvrcpCtSm.obtainMessage( 817 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label); 818 mAvrcpCtSm.sendMessage(msg); 819 } 820 821 // Called by JNI when a track changes and local AvrcpController is registered for updates. 822 private synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes, 823 String[] attribVals) { 824 if (DBG) { 825 Log.d(TAG, "onTrackChanged"); 826 } 827 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 828 if (device != null && !device.equals(mConnectedDevice)) { 829 Log.e(TAG, "onTrackChanged device not found " + address); 830 return; 831 } 832 833 List<Integer> attrList = new ArrayList<>(); 834 for (int attr : attributes) { 835 attrList.add(attr); 836 } 837 List<String> attrValList = Arrays.asList(attribVals); 838 TrackInfo trackInfo = new TrackInfo(attrList, attrValList); 839 if (DBG) { 840 Log.d(TAG, "onTrackChanged " + trackInfo); 841 } 842 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 843 MESSAGE_PROCESS_TRACK_CHANGED, trackInfo); 844 mAvrcpCtSm.sendMessage(msg); 845 } 846 847 // Called by JNI periodically based upon timer to update play position 848 private synchronized void onPlayPositionChanged(byte[] address, int songLen, int currSongPosition) { 849 if (DBG) { 850 Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition); 851 } 852 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 853 if (device != null && !device.equals(mConnectedDevice)) { 854 Log.e(TAG, "onPlayPositionChanged not found device not found " + address); 855 return; 856 } 857 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 858 MESSAGE_PROCESS_PLAY_POS_CHANGED, songLen, currSongPosition); 859 mAvrcpCtSm.sendMessage(msg); 860 } 861 862 // Called by JNI on changes of play status 863 private synchronized void onPlayStatusChanged(byte[] address, byte playStatus) { 864 if (DBG) { 865 Log.d(TAG, "onPlayStatusChanged " + playStatus); 866 } 867 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 868 if (device != null && !device.equals(mConnectedDevice)) { 869 Log.e(TAG, "onPlayStatusChanged not found device not found " + address); 870 return; 871 } 872 int playbackState = PlaybackState.STATE_NONE; 873 switch (playStatus) { 874 case JNI_PLAY_STATUS_STOPPED: 875 playbackState = PlaybackState.STATE_STOPPED; 876 break; 877 case JNI_PLAY_STATUS_PLAYING: 878 playbackState = PlaybackState.STATE_PLAYING; 879 break; 880 case JNI_PLAY_STATUS_PAUSED: 881 playbackState = PlaybackState.STATE_PAUSED; 882 break; 883 case JNI_PLAY_STATUS_FWD_SEEK: 884 playbackState = PlaybackState.STATE_FAST_FORWARDING; 885 break; 886 case JNI_PLAY_STATUS_REV_SEEK: 887 playbackState = PlaybackState.STATE_FAST_FORWARDING; 888 break; 889 default: 890 playbackState = PlaybackState.STATE_NONE; 891 } 892 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 893 MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState); 894 mAvrcpCtSm.sendMessage(msg); 895 } 896 897 // Called by JNI to report remote Player's capabilities 898 private synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen) { 899 if (DBG) { 900 Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen); 901 } 902 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 903 if (device != null && !device.equals(mConnectedDevice)) { 904 Log.e(TAG, "handlePlayerAppSetting not found device not found " + address); 905 return; 906 } 907 PlayerApplicationSettings supportedSettings = PlayerApplicationSettings. 908 makeSupportedSettings(playerAttribRsp); 909 /* Do nothing */ 910 } 911 912 private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen) { 913 if (DBG) { 914 Log.d(TAG, "onPlayerAppSettingChanged "); 915 } 916 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 917 if (device != null && !device.equals(mConnectedDevice)) { 918 Log.e(TAG, "onPlayerAppSettingChanged not found device not found " + address); 919 return; 920 } 921 PlayerApplicationSettings desiredSettings = PlayerApplicationSettings. 922 makeSettings(playerAttribRsp); 923 /* Do nothing */ 924 } 925 926 // Browsing related JNI callbacks. 927 void handleGetFolderItemsRsp(int status, MediaItem[] items) { 928 if (DBG) { 929 Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + 930 " items " + items.length + " items."); 931 } 932 933 if (status == JNI_AVRC_INV_RANGE) { 934 Log.w(TAG, "Sending out of range message."); 935 // Send a special message since this could be used by state machine 936 // to take as a signal that fetch is finished. 937 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 938 MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE); 939 mAvrcpCtSm.sendMessage(msg); 940 return; 941 } 942 943 for (MediaItem item : items) { 944 if (DBG) { 945 Log.d(TAG, "media item: " + item + " uid: " + item.getDescription().getMediaId()); 946 } 947 } 948 ArrayList<MediaItem> itemsList = new ArrayList<>(); 949 for (MediaItem item : items) { 950 itemsList.add(item); 951 } 952 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 953 MESSAGE_PROCESS_GET_FOLDER_ITEMS, itemsList); 954 mAvrcpCtSm.sendMessage(msg); 955 } 956 957 void handleGetPlayerItemsRsp(AvrcpPlayer[] items) { 958 if (DBG) { 959 Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items."); 960 } 961 for (AvrcpPlayer item : items) { 962 if (DBG) { 963 Log.d(TAG, "bt player item: " + item); 964 } 965 } 966 List<AvrcpPlayer> itemsList = new ArrayList<>(); 967 for (AvrcpPlayer p : items) { 968 itemsList.add(p); 969 } 970 971 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 972 MESSAGE_PROCESS_GET_PLAYER_ITEMS, itemsList); 973 mAvrcpCtSm.sendMessage(msg); 974 } 975 976 // JNI Helper functions to convert native objects to java. 977 MediaItem createFromNativeMediaItem( 978 byte[] uid, int type, String name, int[] attrIds, String[] attrVals) { 979 if (DBG) { 980 Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type " + type + " name " + 981 name + " attrids " + attrIds + " attrVals " + attrVals); 982 } 983 MediaDescription.Builder mdb = new MediaDescription.Builder(); 984 985 Bundle mdExtra = new Bundle(); 986 mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid)); 987 mdb.setExtras(mdExtra); 988 989 // Generate a random UUID. We do this since database unaware TGs can send multiple 990 // items with same MEDIA_ITEM_UID_KEY. 991 mdb.setMediaId(UUID.randomUUID().toString()); 992 993 // Concise readable name. 994 mdb.setTitle(name); 995 996 // We skip the attributes since we can query them using UID for the item above 997 // Also MediaDescription does not give an easy way to provide this unless we pass 998 // it as an MediaMetadata which is put inside the extras. 999 return new MediaItem(mdb.build(), MediaItem.FLAG_PLAYABLE); 1000 } 1001 1002 MediaItem createFromNativeFolderItem( 1003 byte[] uid, int type, String name, int playable) { 1004 if (DBG) { 1005 Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type + 1006 " name " + name + " playable " + playable); 1007 } 1008 MediaDescription.Builder mdb = new MediaDescription.Builder(); 1009 1010 // Covert the byte to a hex string. The coversion can be done back here to a 1011 // byte array when needed. 1012 Bundle mdExtra = new Bundle(); 1013 mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid)); 1014 mdb.setExtras(mdExtra); 1015 1016 // Generate a random UUID. We do this since database unaware TGs can send multiple 1017 // items with same MEDIA_ITEM_UID_KEY. 1018 mdb.setMediaId(UUID.randomUUID().toString()); 1019 1020 // Concise readable name. 1021 mdb.setTitle(name); 1022 1023 return new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE); 1024 } 1025 1026 AvrcpPlayer createFromNativePlayerItem( 1027 int id, String name, byte[] transportFlags, int playStatus, int playerType) { 1028 if (DBG) { 1029 Log.d(TAG, "createFromNativePlayerItem name: " + name + " transportFlags " + 1030 transportFlags + " play status " + playStatus + " player type " + 1031 playerType); 1032 } 1033 AvrcpPlayer player = new AvrcpPlayer(id, name, 0, playStatus, playerType); 1034 return player; 1035 } 1036 1037 private void handleChangeFolderRsp(int count) { 1038 if (DBG) { 1039 Log.d(TAG, "handleChangeFolderRsp count: " + count); 1040 } 1041 Message msg = mAvrcpCtSm.obtainMessage( 1042 AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, count); 1043 mAvrcpCtSm.sendMessage(msg); 1044 } 1045 1046 private void handleSetBrowsedPlayerRsp(int items, int depth) { 1047 if (DBG) { 1048 Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth); 1049 } 1050 Message msg = mAvrcpCtSm.obtainMessage( 1051 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth); 1052 mAvrcpCtSm.sendMessage(msg); 1053 } 1054 1055 private void handleSetAddressedPlayerRsp(int status) { 1056 if (DBG) { 1057 Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status); 1058 } 1059 Message msg = mAvrcpCtSm.obtainMessage( 1060 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER); 1061 mAvrcpCtSm.sendMessage(msg); 1062 } 1063 1064 @Override 1065 public void dump(StringBuilder sb) { 1066 super.dump(sb); 1067 mAvrcpCtSm.dump(sb); 1068 } 1069 1070 public static String byteUIDToHexString(byte[] uid) { 1071 StringBuilder sb = new StringBuilder(); 1072 for (byte b : uid) { 1073 sb.append(String.format("%02X", b)); 1074 } 1075 return sb.toString(); 1076 } 1077 1078 public static byte[] hexStringToByteUID(String uidStr) { 1079 if (uidStr == null) { 1080 Log.e(TAG, "Null hex string."); 1081 return EMPTY_UID; 1082 } else if (uidStr.length() % 2 == 1) { 1083 // Odd length strings should not be possible. 1084 Log.e(TAG, "Odd length hex string " + uidStr); 1085 return EMPTY_UID; 1086 } 1087 int len = uidStr.length(); 1088 byte[] data = new byte[len / 2]; 1089 for (int i = 0; i < len; i += 2) { 1090 data[i / 2] = (byte) ((Character.digit(uidStr.charAt(i), 16) << 4) 1091 + Character.digit(uidStr.charAt(i + 1), 16)); 1092 } 1093 return data; 1094 } 1095 1096 private native static void classInitNative(); 1097 1098 private native void initNative(); 1099 1100 private native void cleanupNative(); 1101 1102 native static boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState); 1103 1104 native static boolean sendGroupNavigationCommandNative(byte[] address, int keyCode, 1105 int keyState); 1106 1107 native static void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib, 1108 byte[] atttibIds, byte[] attribVal); 1109 1110 /* This api is used to send response to SET_ABS_VOL_CMD */ 1111 native static void sendAbsVolRspNative(byte[] address, int absVol, int label); 1112 1113 /* This api is used to inform remote for any volume level changes */ 1114 native static void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol, 1115 int label); 1116 1117 /* API used to fetch the playback state */ 1118 native static void getPlaybackStateNative(byte[] address); 1119 /* API used to fetch the current now playing list */ 1120 native static void getNowPlayingListNative(byte[] address, byte start, byte end); 1121 /* API used to fetch the current folder's listing */ 1122 native static void getFolderListNative(byte[] address, byte start, byte end); 1123 /* API used to fetch the listing of players */ 1124 native static void getPlayerListNative(byte[] address, byte start, byte end); 1125 /* API used to change the folder */ 1126 native static void changeFolderPathNative(byte[] address, byte direction, byte[] uid); 1127 native static void playItemNative( 1128 byte[] address, byte scope, byte[] uid, int uidCounter); 1129 native static void setBrowsedPlayerNative(byte[] address, int playerId); 1130 native static void setAddressedPlayerNative(byte[] address, int playerId); 1131} 1132