AvrcpControllerService.java revision 228d402643019482081637023d628794a08d32ea
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 boolean getChildren( 436 BluetoothDevice device, String parentMediaId, int start, int items) { 437 if (DBG) { 438 Log.d(TAG, "getChildren device = " + device + " parent " + parentMediaId); 439 } 440 441 if (device == null) { 442 Log.e(TAG, "getChildren device is null"); 443 return false; 444 } 445 446 if (!device.equals(mConnectedDevice)) { 447 Log.e(TAG, "getChildren device " + device + " does not match " + 448 mConnectedDevice); 449 return false; 450 } 451 452 if (!mBrowseConnected) { 453 Log.e(TAG, "getChildren browse not yet connected"); 454 return false; 455 } 456 457 if (!mAvrcpCtSm.isConnected()) { 458 return false; 459 } 460 mAvrcpCtSm.getChildren(parentMediaId, start, items); 461 return true; 462 } 463 464 public synchronized boolean getNowPlayingList( 465 BluetoothDevice device, String id, int start, int items) { 466 if (DBG) { 467 Log.d(TAG, "getNowPlayingList device = " + device + " start = " + start + 468 "items = " + items); 469 } 470 471 if (device == null) { 472 Log.e(TAG, "getNowPlayingList device is null"); 473 return false; 474 } 475 476 if (!device.equals(mConnectedDevice)) { 477 Log.e(TAG, "getNowPlayingList device " + device + " does not match " + 478 mConnectedDevice); 479 return false; 480 } 481 482 if (!mBrowseConnected) { 483 Log.e(TAG, "getNowPlayingList browse not yet connected"); 484 return false; 485 } 486 487 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 488 489 Message msg = mAvrcpCtSm.obtainMessage( 490 AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST, start, items, id); 491 mAvrcpCtSm.sendMessage(msg); 492 return true; 493 } 494 495 public synchronized boolean getFolderList( 496 BluetoothDevice device, String id, int start, int items) { 497 if (DBG) { 498 Log.d(TAG, "getFolderListing device = " + device + " start = " + start + 499 "items = " + items); 500 } 501 502 if (device == null) { 503 Log.e(TAG, "getFolderListing device is null"); 504 return false; 505 } 506 507 if (!device.equals(mConnectedDevice)) { 508 Log.e(TAG, "getFolderListing device " + device + " does not match " + mConnectedDevice); 509 return false; 510 } 511 512 if (!mBrowseConnected) { 513 Log.e(TAG, "getFolderListing browse not yet connected"); 514 return false; 515 } 516 517 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 518 519 Message msg = mAvrcpCtSm.obtainMessage( 520 AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start, items, id); 521 mAvrcpCtSm.sendMessage(msg); 522 return true; 523 } 524 525 public synchronized boolean getPlayerList(BluetoothDevice device, int start, int items) { 526 if (DBG) { 527 Log.d(TAG, "getPlayerList device = " + device + " start = " + start + 528 "items = " + items); 529 } 530 531 if (device == null) { 532 Log.e(TAG, "getPlayerList device is null"); 533 return false; 534 } 535 536 if (!device.equals(mConnectedDevice)) { 537 Log.e(TAG, "getPlayerList device " + device + " does not match " + mConnectedDevice); 538 return false; 539 } 540 541 if (!mBrowseConnected) { 542 Log.e(TAG, "getPlayerList browse not yet connected"); 543 return false; 544 } 545 546 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 547 548 Message msg = mAvrcpCtSm.obtainMessage( 549 AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items); 550 mAvrcpCtSm.sendMessage(msg); 551 return true; 552 } 553 554 public synchronized boolean changeFolderPath( 555 BluetoothDevice device, int direction, String uid, String fid) { 556 if (DBG) { 557 Log.d(TAG, "changeFolderPath device = " + device + " direction " + 558 direction + " uid " + uid); 559 } 560 561 if (device == null) { 562 Log.e(TAG, "changeFolderPath device is null"); 563 return false; 564 } 565 566 if (!device.equals(mConnectedDevice)) { 567 Log.e(TAG, "changeFolderPath device " + device + " does not match " + 568 mConnectedDevice); 569 return false; 570 } 571 572 if (!mBrowseConnected) { 573 Log.e(TAG, "changeFolderPath browse not yet connected"); 574 return false; 575 } 576 577 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 578 579 Bundle b = new Bundle(); 580 b.putString(EXTRA_FOLDER_ID, fid); 581 b.putString(EXTRA_FOLDER_BT_ID, uid); 582 Message msg = mAvrcpCtSm.obtainMessage( 583 AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH, direction, 0, b); 584 mAvrcpCtSm.sendMessage(msg); 585 return true; 586 } 587 588 public synchronized boolean setBrowsedPlayer(BluetoothDevice device, int id, String fid) { 589 if (DBG) { 590 Log.d(TAG, "setBrowsedPlayer device = " + device + " id" + id + " fid " + fid); 591 } 592 593 if (device == null) { 594 Log.e(TAG, "setBrowsedPlayer device is null"); 595 return false; 596 } 597 598 if (!device.equals(mConnectedDevice)) { 599 Log.e(TAG, "changeFolderPath device " + device + " does not match " + 600 mConnectedDevice); 601 return false; 602 } 603 604 if (!mBrowseConnected) { 605 Log.e(TAG, "setBrowsedPlayer browse not yet connected"); 606 return false; 607 } 608 609 enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 610 611 Message msg = mAvrcpCtSm.obtainMessage( 612 AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, id, 0, fid); 613 mAvrcpCtSm.sendMessage(msg); 614 return true; 615 } 616 617 public synchronized void fetchAttrAndPlayItem(BluetoothDevice device, String uid) { 618 if (DBG) { 619 Log.d(TAG, "fetchAttrAndPlayItem device = " + device + " uid " + uid); 620 } 621 622 if (device == null) { 623 Log.e(TAG, "fetchAttrAndPlayItem device is null"); 624 return; 625 } 626 627 if (!device.equals(mConnectedDevice)) { 628 Log.e(TAG, "fetchAttrAndPlayItem device " + device + " does not match " + 629 mConnectedDevice); 630 return; 631 } 632 633 if (!mBrowseConnected) { 634 Log.e(TAG, "fetchAttrAndPlayItem browse not yet connected"); 635 return; 636 } 637 mAvrcpCtSm.fetchAttrAndPlayItem(uid); 638 } 639 640 //Binder object: Must be static class or memory leak may occur 641 private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub 642 implements IProfileServiceBinder { 643 644 private AvrcpControllerService mService; 645 646 private AvrcpControllerService getService() { 647 if (!Utils.checkCaller()) { 648 Log.w(TAG, "AVRCP call not allowed for non-active user"); 649 return null; 650 } 651 652 if (mService != null && mService.isAvailable()) { 653 return mService; 654 } 655 return null; 656 } 657 658 BluetoothAvrcpControllerBinder(AvrcpControllerService svc) { 659 mService = svc; 660 } 661 662 public boolean cleanup() { 663 mService = null; 664 return true; 665 } 666 667 @Override 668 public List<BluetoothDevice> getConnectedDevices() { 669 AvrcpControllerService service = getService(); 670 if (service == null) { 671 return new ArrayList<BluetoothDevice>(0); 672 } 673 return service.getConnectedDevices(); 674 } 675 676 @Override 677 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 678 AvrcpControllerService service = getService(); 679 if (service == null) { 680 return new ArrayList<BluetoothDevice>(0); 681 } 682 return service.getDevicesMatchingConnectionStates(states); 683 } 684 685 @Override 686 public int getConnectionState(BluetoothDevice device) { 687 AvrcpControllerService service = getService(); 688 if (service == null) { 689 return BluetoothProfile.STATE_DISCONNECTED; 690 } 691 692 if (device == null) { 693 throw new IllegalStateException("Device cannot be null!"); 694 } 695 696 return service.getConnectionState(device); 697 } 698 699 @Override 700 public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) { 701 Log.v(TAG, "Binder Call: sendGroupNavigationCmd"); 702 AvrcpControllerService service = getService(); 703 if (service == null) { 704 return; 705 } 706 707 if (device == null) { 708 throw new IllegalStateException("Device cannot be null!"); 709 } 710 711 service.sendGroupNavigationCmd(device, keyCode, keyState); 712 } 713 714 @Override 715 public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) { 716 Log.v(TAG, "Binder Call: getPlayerApplicationSetting "); 717 AvrcpControllerService service = getService(); 718 if (service == null) { 719 return null; 720 } 721 722 if (device == null) { 723 throw new IllegalStateException("Device cannot be null!"); 724 } 725 726 return service.getPlayerSettings(device); 727 } 728 729 @Override 730 public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) { 731 Log.v(TAG, "Binder Call: setPlayerApplicationSetting "); 732 AvrcpControllerService service = getService(); 733 if (service == null) { 734 return false; 735 } 736 return service.setPlayerApplicationSetting(plAppSetting); 737 } 738 } 739 740 // Called by JNI when a passthrough key was received. 741 private void handlePassthroughRsp(int id, int keyState, byte[] address) { 742 Log.d(TAG, "passthrough response received as: key: " + id + " state: " + keyState + 743 "address:" + address); 744 } 745 746 private void handleGroupNavigationRsp(int id, int keyState) { 747 Log.d(TAG, "group navigation response received as: key: " + id + " state: " + keyState); 748 } 749 750 // Called by JNI when a device has connected or disconnected. 751 private synchronized void onConnectionStateChanged( 752 boolean rc_connected, boolean br_connected, byte[] address) { 753 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 754 Log.d(TAG, "onConnectionStateChanged " + rc_connected + " " + br_connected + 755 device + " conn device " + mConnectedDevice); 756 if (device == null) { 757 Log.e(TAG, "onConnectionStateChanged Device is null"); 758 return; 759 } 760 761 // Adjust the AVRCP connection state. 762 int oldState = (device.equals(mConnectedDevice) ? BluetoothProfile.STATE_CONNECTED : 763 BluetoothProfile.STATE_DISCONNECTED); 764 int newState = (rc_connected ? BluetoothProfile.STATE_CONNECTED : 765 BluetoothProfile.STATE_DISCONNECTED); 766 767 if (rc_connected && oldState == BluetoothProfile.STATE_DISCONNECTED) { 768 /* AVRCPControllerService supports single connection */ 769 if (mConnectedDevice != null) { 770 Log.d(TAG, "A Connection already exists, returning"); 771 return; 772 } 773 mConnectedDevice = device; 774 Message msg = mAvrcpCtSm.obtainMessage( 775 AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState, 776 oldState, device); 777 mAvrcpCtSm.sendMessage(msg); 778 } else if (!rc_connected && oldState == BluetoothProfile.STATE_CONNECTED) { 779 mConnectedDevice = null; 780 Message msg = mAvrcpCtSm.obtainMessage( 781 AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState, 782 oldState, device); 783 mAvrcpCtSm.sendMessage(msg); 784 } 785 786 // Adjust the browse connection state. If RC is connected we should have already sent the 787 // connection status out. 788 if (rc_connected && br_connected) { 789 mBrowseConnected = true; 790 Message msg = mAvrcpCtSm.obtainMessage( 791 AvrcpControllerStateMachine.MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE); 792 msg.arg1 = 1; 793 msg.obj = device; 794 mAvrcpCtSm.sendMessage(msg); 795 } 796 } 797 798 // Called by JNI to notify Avrcp of features supported by the Remote device. 799 private void getRcFeatures(byte[] address, int features) { 800 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 801 Message msg = mAvrcpCtSm.obtainMessage( 802 AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES, features, 0, device); 803 mAvrcpCtSm.sendMessage(msg); 804 } 805 806 // Called by JNI 807 private void setPlayerAppSettingRsp(byte[] address, byte accepted) { 808 /* Do Nothing. */ 809 } 810 811 // Called by JNI when remote wants to receive absolute volume notifications. 812 private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) { 813 Log.d(TAG, "handleRegisterNotificationAbsVol "); 814 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 815 if (device != null && !device.equals(mConnectedDevice)) { 816 Log.e(TAG, "handleRegisterNotificationAbsVol device not found " + address); 817 return; 818 } 819 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 820 MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION, (int) label, 0); 821 mAvrcpCtSm.sendMessage(msg); 822 } 823 824 // Called by JNI when remote wants to set absolute volume. 825 private synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) { 826 Log.d(TAG, "handleSetAbsVolume "); 827 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 828 if (device != null && !device.equals(mConnectedDevice)) { 829 Log.e(TAG, "handleSetAbsVolume device not found " + address); 830 return; 831 } 832 Message msg = mAvrcpCtSm.obtainMessage( 833 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label); 834 mAvrcpCtSm.sendMessage(msg); 835 } 836 837 // Called by JNI when a track changes and local AvrcpController is registered for updates. 838 private synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes, 839 String[] attribVals) { 840 if (DBG) { 841 Log.d(TAG, "onTrackChanged"); 842 } 843 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 844 if (device != null && !device.equals(mConnectedDevice)) { 845 Log.e(TAG, "onTrackChanged device not found " + address); 846 return; 847 } 848 849 List<Integer> attrList = new ArrayList<>(); 850 for (int attr : attributes) { 851 attrList.add(attr); 852 } 853 List<String> attrValList = Arrays.asList(attribVals); 854 TrackInfo trackInfo = new TrackInfo(attrList, attrValList); 855 if (DBG) { 856 Log.d(TAG, "onTrackChanged " + trackInfo); 857 } 858 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 859 MESSAGE_PROCESS_TRACK_CHANGED, trackInfo); 860 mAvrcpCtSm.sendMessage(msg); 861 } 862 863 // Called by JNI periodically based upon timer to update play position 864 private synchronized void onPlayPositionChanged(byte[] address, int songLen, int currSongPosition) { 865 if (DBG) { 866 Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition); 867 } 868 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 869 if (device != null && !device.equals(mConnectedDevice)) { 870 Log.e(TAG, "onPlayPositionChanged not found device not found " + address); 871 return; 872 } 873 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 874 MESSAGE_PROCESS_PLAY_POS_CHANGED, songLen, currSongPosition); 875 mAvrcpCtSm.sendMessage(msg); 876 } 877 878 // Called by JNI on changes of play status 879 private synchronized void onPlayStatusChanged(byte[] address, byte playStatus) { 880 if (DBG) { 881 Log.d(TAG, "onPlayStatusChanged " + playStatus); 882 } 883 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 884 if (device != null && !device.equals(mConnectedDevice)) { 885 Log.e(TAG, "onPlayStatusChanged not found device not found " + address); 886 return; 887 } 888 int playbackState = PlaybackState.STATE_NONE; 889 switch (playStatus) { 890 case JNI_PLAY_STATUS_STOPPED: 891 playbackState = PlaybackState.STATE_STOPPED; 892 break; 893 case JNI_PLAY_STATUS_PLAYING: 894 playbackState = PlaybackState.STATE_PLAYING; 895 break; 896 case JNI_PLAY_STATUS_PAUSED: 897 playbackState = PlaybackState.STATE_PAUSED; 898 break; 899 case JNI_PLAY_STATUS_FWD_SEEK: 900 playbackState = PlaybackState.STATE_FAST_FORWARDING; 901 break; 902 case JNI_PLAY_STATUS_REV_SEEK: 903 playbackState = PlaybackState.STATE_FAST_FORWARDING; 904 break; 905 default: 906 playbackState = PlaybackState.STATE_NONE; 907 } 908 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 909 MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState); 910 mAvrcpCtSm.sendMessage(msg); 911 } 912 913 // Called by JNI to report remote Player's capabilities 914 private synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen) { 915 if (DBG) { 916 Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen); 917 } 918 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 919 if (device != null && !device.equals(mConnectedDevice)) { 920 Log.e(TAG, "handlePlayerAppSetting not found device not found " + address); 921 return; 922 } 923 PlayerApplicationSettings supportedSettings = PlayerApplicationSettings. 924 makeSupportedSettings(playerAttribRsp); 925 /* Do nothing */ 926 } 927 928 private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen) { 929 if (DBG) { 930 Log.d(TAG, "onPlayerAppSettingChanged "); 931 } 932 BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address); 933 if (device != null && !device.equals(mConnectedDevice)) { 934 Log.e(TAG, "onPlayerAppSettingChanged not found device not found " + address); 935 return; 936 } 937 PlayerApplicationSettings desiredSettings = PlayerApplicationSettings. 938 makeSettings(playerAttribRsp); 939 /* Do nothing */ 940 } 941 942 // Browsing related JNI callbacks. 943 void handleGetFolderItemsRsp(int status, MediaItem[] items) { 944 if (DBG) { 945 Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + 946 " items " + items.length + " items."); 947 } 948 949 if (status == JNI_AVRC_INV_RANGE) { 950 Log.w(TAG, "Sending out of range message."); 951 // Send a special message since this could be used by state machine 952 // to take as a signal that fetch is finished. 953 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 954 MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE); 955 mAvrcpCtSm.sendMessage(msg); 956 return; 957 } 958 959 for (MediaItem item : items) { 960 if (DBG) { 961 Log.d(TAG, "media item: " + item + " uid: " + item.getDescription().getMediaId()); 962 } 963 } 964 ArrayList<MediaItem> itemsList = new ArrayList<>(); 965 for (MediaItem item : items) { 966 itemsList.add(item); 967 } 968 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 969 MESSAGE_PROCESS_GET_FOLDER_ITEMS, itemsList); 970 mAvrcpCtSm.sendMessage(msg); 971 } 972 973 void handleGetPlayerItemsRsp(AvrcpPlayer[] items) { 974 if (DBG) { 975 Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items."); 976 } 977 for (AvrcpPlayer item : items) { 978 if (DBG) { 979 Log.d(TAG, "bt player item: " + item); 980 } 981 } 982 List<AvrcpPlayer> itemsList = new ArrayList<>(); 983 for (AvrcpPlayer p : items) { 984 itemsList.add(p); 985 } 986 987 Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine. 988 MESSAGE_PROCESS_GET_PLAYER_ITEMS, itemsList); 989 mAvrcpCtSm.sendMessage(msg); 990 } 991 992 // JNI Helper functions to convert native objects to java. 993 MediaItem createFromNativeMediaItem( 994 byte[] uid, int type, String name, int[] attrIds, String[] attrVals) { 995 if (DBG) { 996 Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type " + type + " name " + 997 name + " attrids " + attrIds + " attrVals " + attrVals); 998 } 999 MediaDescription.Builder mdb = new MediaDescription.Builder(); 1000 1001 Bundle mdExtra = new Bundle(); 1002 mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid)); 1003 mdb.setExtras(mdExtra); 1004 1005 // Generate a random UUID. We do this since database unaware TGs can send multiple 1006 // items with same MEDIA_ITEM_UID_KEY. 1007 mdb.setMediaId(UUID.randomUUID().toString()); 1008 1009 // Concise readable name. 1010 mdb.setTitle(name); 1011 1012 // We skip the attributes since we can query them using UID for the item above 1013 // Also MediaDescription does not give an easy way to provide this unless we pass 1014 // it as an MediaMetadata which is put inside the extras. 1015 return new MediaItem(mdb.build(), MediaItem.FLAG_PLAYABLE); 1016 } 1017 1018 MediaItem createFromNativeFolderItem( 1019 byte[] uid, int type, String name, int playable) { 1020 if (DBG) { 1021 Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type + 1022 " name " + name + " playable " + playable); 1023 } 1024 MediaDescription.Builder mdb = new MediaDescription.Builder(); 1025 1026 // Covert the byte to a hex string. The coversion can be done back here to a 1027 // byte array when needed. 1028 Bundle mdExtra = new Bundle(); 1029 mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid)); 1030 mdb.setExtras(mdExtra); 1031 1032 // Generate a random UUID. We do this since database unaware TGs can send multiple 1033 // items with same MEDIA_ITEM_UID_KEY. 1034 mdb.setMediaId(UUID.randomUUID().toString()); 1035 1036 // Concise readable name. 1037 mdb.setTitle(name); 1038 1039 return new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE); 1040 } 1041 1042 AvrcpPlayer createFromNativePlayerItem( 1043 int id, String name, byte[] transportFlags, int playStatus, int playerType) { 1044 if (DBG) { 1045 Log.d(TAG, "createFromNativePlayerItem name: " + name + " transportFlags " + 1046 transportFlags + " play status " + playStatus + " player type " + 1047 playerType); 1048 } 1049 AvrcpPlayer player = new AvrcpPlayer(id, name, 0, playStatus, playerType); 1050 return player; 1051 } 1052 1053 private void handleChangeFolderRsp(int count) { 1054 if (DBG) { 1055 Log.d(TAG, "handleChangeFolderRsp count: " + count); 1056 } 1057 Message msg = mAvrcpCtSm.obtainMessage( 1058 AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, count); 1059 mAvrcpCtSm.sendMessage(msg); 1060 } 1061 1062 private void handleSetBrowsedPlayerRsp(int items, int depth) { 1063 if (DBG) { 1064 Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth); 1065 } 1066 Message msg = mAvrcpCtSm.obtainMessage( 1067 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth); 1068 mAvrcpCtSm.sendMessage(msg); 1069 } 1070 1071 private void handleSetAddressedPlayerRsp(int status) { 1072 if (DBG) { 1073 Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status); 1074 } 1075 Message msg = mAvrcpCtSm.obtainMessage( 1076 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER); 1077 mAvrcpCtSm.sendMessage(msg); 1078 } 1079 1080 @Override 1081 public void dump(StringBuilder sb) { 1082 super.dump(sb); 1083 mAvrcpCtSm.dump(sb); 1084 } 1085 1086 public static String byteUIDToHexString(byte[] uid) { 1087 StringBuilder sb = new StringBuilder(); 1088 for (byte b : uid) { 1089 sb.append(String.format("%02X", b)); 1090 } 1091 return sb.toString(); 1092 } 1093 1094 public static byte[] hexStringToByteUID(String uidStr) { 1095 if (uidStr == null) { 1096 Log.e(TAG, "Null hex string."); 1097 return EMPTY_UID; 1098 } else if (uidStr.length() % 2 == 1) { 1099 // Odd length strings should not be possible. 1100 Log.e(TAG, "Odd length hex string " + uidStr); 1101 return EMPTY_UID; 1102 } 1103 int len = uidStr.length(); 1104 byte[] data = new byte[len / 2]; 1105 for (int i = 0; i < len; i += 2) { 1106 data[i / 2] = (byte) ((Character.digit(uidStr.charAt(i), 16) << 4) 1107 + Character.digit(uidStr.charAt(i + 1), 16)); 1108 } 1109 return data; 1110 } 1111 1112 private native static void classInitNative(); 1113 1114 private native void initNative(); 1115 1116 private native void cleanupNative(); 1117 1118 native static boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState); 1119 1120 native static boolean sendGroupNavigationCommandNative(byte[] address, int keyCode, 1121 int keyState); 1122 1123 native static void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib, 1124 byte[] atttibIds, byte[] attribVal); 1125 1126 /* This api is used to send response to SET_ABS_VOL_CMD */ 1127 native static void sendAbsVolRspNative(byte[] address, int absVol, int label); 1128 1129 /* This api is used to inform remote for any volume level changes */ 1130 native static void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol, 1131 int label); 1132 1133 /* API used to fetch the playback state */ 1134 native static void getPlaybackStateNative(byte[] address); 1135 /* API used to fetch the current now playing list */ 1136 native static void getNowPlayingListNative(byte[] address, byte start, byte end); 1137 /* API used to fetch the current folder's listing */ 1138 native static void getFolderListNative(byte[] address, byte start, byte end); 1139 /* API used to fetch the listing of players */ 1140 native static void getPlayerListNative(byte[] address, byte start, byte end); 1141 /* API used to change the folder */ 1142 native static void changeFolderPathNative(byte[] address, byte direction, byte[] uid); 1143 native static void playItemNative( 1144 byte[] address, byte scope, byte[] uid, int uidCounter); 1145 native static void setBrowsedPlayerNative(byte[] address, int playerId); 1146 native static void setAddressedPlayerNative(byte[] address, int playerId); 1147} 1148