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