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.annotation.RequiresPermission; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothAvrcpPlayerSettings; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothProfile; 24 import android.bluetooth.IBluetoothAvrcpController; 25 import android.content.Attributable; 26 import android.content.AttributionSource; 27 import android.content.Intent; 28 import android.support.v4.media.MediaBrowserCompat.MediaItem; 29 import android.support.v4.media.session.PlaybackStateCompat; 30 import android.util.Log; 31 32 import com.android.bluetooth.R; 33 import com.android.bluetooth.Utils; 34 import com.android.bluetooth.a2dpsink.A2dpSinkService; 35 import com.android.bluetooth.btservice.AdapterService; 36 import com.android.bluetooth.btservice.ProfileService; 37 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.Collections; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.Set; 44 import java.util.UUID; 45 import java.util.concurrent.ConcurrentHashMap; 46 47 /** 48 * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application. 49 */ 50 public class AvrcpControllerService extends ProfileService { 51 static final String TAG = "AvrcpControllerService"; 52 static final int MAXIMUM_CONNECTED_DEVICES = 5; 53 static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 54 static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); 55 56 /* 57 * Play State Values from JNI 58 */ 59 private static final byte JNI_PLAY_STATUS_STOPPED = 0x00; 60 private static final byte JNI_PLAY_STATUS_PLAYING = 0x01; 61 private static final byte JNI_PLAY_STATUS_PAUSED = 0x02; 62 private static final byte JNI_PLAY_STATUS_FWD_SEEK = 0x03; 63 private static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04; 64 private static final byte JNI_PLAY_STATUS_ERROR = -1; 65 66 /* Folder/Media Item scopes. 67 * Keep in sync with AVRCP 1.6 sec. 6.10.1 68 */ 69 public static final byte BROWSE_SCOPE_PLAYER_LIST = 0x00; 70 public static final byte BROWSE_SCOPE_VFS = 0x01; 71 public static final byte BROWSE_SCOPE_SEARCH = 0x02; 72 public static final byte BROWSE_SCOPE_NOW_PLAYING = 0x03; 73 74 /* Folder navigation directions 75 * This is borrowed from AVRCP 1.6 spec and must be kept with same values 76 */ 77 public static final byte FOLDER_NAVIGATION_DIRECTION_UP = 0x00; 78 public static final byte FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01; 79 80 /* 81 * KeyCoded for Pass Through Commands 82 */ 83 public static final int PASS_THRU_CMD_ID_PLAY = 0x44; 84 public static final int PASS_THRU_CMD_ID_PAUSE = 0x46; 85 public static final int PASS_THRU_CMD_ID_VOL_UP = 0x41; 86 public static final int PASS_THRU_CMD_ID_VOL_DOWN = 0x42; 87 public static final int PASS_THRU_CMD_ID_STOP = 0x45; 88 public static final int PASS_THRU_CMD_ID_FF = 0x49; 89 public static final int PASS_THRU_CMD_ID_REWIND = 0x48; 90 public static final int PASS_THRU_CMD_ID_FORWARD = 0x4B; 91 public static final int PASS_THRU_CMD_ID_BACKWARD = 0x4C; 92 93 /* Key State Variables */ 94 public static final int KEY_STATE_PRESSED = 0; 95 public static final int KEY_STATE_RELEASED = 1; 96 97 /* Active Device State Variables */ 98 public static final int DEVICE_STATE_INACTIVE = 0; 99 public static final int DEVICE_STATE_ACTIVE = 1; 100 101 static BrowseTree sBrowseTree; 102 private static AvrcpControllerService sService; 103 104 private AdapterService mAdapterService; 105 106 protected Map<BluetoothDevice, AvrcpControllerStateMachine> mDeviceStateMap = 107 new ConcurrentHashMap<>(1); 108 private BluetoothDevice mActiveDevice = null; 109 private final Object mActiveDeviceLock = new Object(); 110 111 private boolean mCoverArtEnabled = false; 112 protected AvrcpCoverArtManager mCoverArtManager; 113 114 private class ImageDownloadCallback implements AvrcpCoverArtManager.Callback { 115 @Override onImageDownloadComplete(BluetoothDevice device, AvrcpCoverArtManager.DownloadEvent event)116 public void onImageDownloadComplete(BluetoothDevice device, 117 AvrcpCoverArtManager.DownloadEvent event) { 118 if (DBG) { 119 Log.d(TAG, "Image downloaded [device: " + device + ", uuid: " + event.getUuid() 120 + ", uri: " + event.getUri()); 121 } 122 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 123 if (stateMachine == null) { 124 Log.e(TAG, "No state machine found for device " + device); 125 mCoverArtManager.removeImage(device, event.getUuid()); 126 return; 127 } 128 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_IMAGE_DOWNLOADED, 129 event); 130 } 131 } 132 133 static { classInitNative()134 classInitNative(); 135 } 136 137 @Override start()138 protected synchronized boolean start() { 139 initNative(); 140 mAdapterService = AdapterService.getAdapterService(); 141 mCoverArtEnabled = getResources().getBoolean(R.bool.avrcp_controller_enable_cover_art); 142 if (mCoverArtEnabled) { 143 mCoverArtManager = new AvrcpCoverArtManager(this, new ImageDownloadCallback()); 144 } 145 sBrowseTree = new BrowseTree(null); 146 sService = this; 147 148 // Start the media browser service. 149 Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class); 150 startService(startIntent); 151 setActiveDevice(null); 152 return true; 153 } 154 155 @Override stop()156 protected synchronized boolean stop() { 157 setActiveDevice(null); 158 Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class); 159 stopService(stopIntent); 160 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 161 stateMachine.quitNow(); 162 } 163 164 sService = null; 165 sBrowseTree = null; 166 if (mCoverArtManager != null) { 167 mCoverArtManager.cleanup(); 168 mCoverArtManager = null; 169 } 170 return true; 171 } 172 getAvrcpControllerService()173 public static AvrcpControllerService getAvrcpControllerService() { 174 return sService; 175 } 176 177 /** 178 * Get the current active device 179 */ getActiveDevice()180 public BluetoothDevice getActiveDevice() { 181 synchronized (mActiveDeviceLock) { 182 return mActiveDevice; 183 } 184 } 185 186 /** 187 * Set the current active device, notify devices of activity status 188 */ setActiveDevice(BluetoothDevice device)189 private boolean setActiveDevice(BluetoothDevice device) { 190 A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService(); 191 if (a2dpSinkService == null) { 192 return false; 193 } 194 195 BluetoothDevice currentActiveDevice = getActiveDevice(); 196 if ((device == null && currentActiveDevice == null) 197 || (device != null && device.equals(currentActiveDevice))) { 198 return true; 199 } 200 201 // Try and update the active device 202 synchronized (mActiveDeviceLock) { 203 if (a2dpSinkService.setActiveDevice(device)) { 204 mActiveDevice = device; 205 206 // Pause the old active device 207 if (currentActiveDevice != null) { 208 AvrcpControllerStateMachine oldStateMachine = 209 getStateMachine(currentActiveDevice); 210 if (oldStateMachine != null) { 211 oldStateMachine.setDeviceState(DEVICE_STATE_INACTIVE); 212 } 213 } 214 215 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 216 if (stateMachine != null) { 217 stateMachine.setDeviceState(DEVICE_STATE_ACTIVE); 218 } else { 219 BluetoothMediaBrowserService.reset(); 220 } 221 return true; 222 } 223 } 224 return false; 225 } 226 toPlaybackStateFromJni(int fromJni)227 private int toPlaybackStateFromJni(int fromJni) { 228 int playbackState = PlaybackStateCompat.STATE_NONE; 229 switch (fromJni) { 230 case JNI_PLAY_STATUS_STOPPED: 231 playbackState = PlaybackStateCompat.STATE_STOPPED; 232 break; 233 case JNI_PLAY_STATUS_PLAYING: 234 playbackState = PlaybackStateCompat.STATE_PLAYING; 235 break; 236 case JNI_PLAY_STATUS_PAUSED: 237 playbackState = PlaybackStateCompat.STATE_PAUSED; 238 break; 239 case JNI_PLAY_STATUS_FWD_SEEK: 240 playbackState = PlaybackStateCompat.STATE_FAST_FORWARDING; 241 break; 242 case JNI_PLAY_STATUS_REV_SEEK: 243 playbackState = PlaybackStateCompat.STATE_REWINDING; 244 break; 245 default: 246 playbackState = PlaybackStateCompat.STATE_NONE; 247 } 248 return playbackState; 249 } 250 newStateMachine(BluetoothDevice device)251 protected AvrcpControllerStateMachine newStateMachine(BluetoothDevice device) { 252 return new AvrcpControllerStateMachine(device, this); 253 } 254 getCurrentMetadataIfNoCoverArt(BluetoothDevice device)255 protected void getCurrentMetadataIfNoCoverArt(BluetoothDevice device) { 256 if (device == null) return; 257 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 258 if (stateMachine == null) return; 259 AvrcpItem track = stateMachine.getCurrentTrack(); 260 if (track != null && track.getCoverArtLocation() == null) { 261 getCurrentMetadataNative(Utils.getByteAddress(device)); 262 } 263 } 264 refreshContents(BrowseTree.BrowseNode node)265 private void refreshContents(BrowseTree.BrowseNode node) { 266 BluetoothDevice device = node.getDevice(); 267 if (device == null) { 268 return; 269 } 270 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 271 if (stateMachine != null) { 272 stateMachine.requestContents(node); 273 } 274 } 275 playItem(String parentMediaId)276 void playItem(String parentMediaId) { 277 if (DBG) Log.d(TAG, "playItem(" + parentMediaId + ")"); 278 // Check if the requestedNode is a player rather than a song 279 BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId); 280 if (requestedNode == null) { 281 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 282 // Check each state machine for the song and then play it 283 requestedNode = stateMachine.findNode(parentMediaId); 284 if (requestedNode != null) { 285 if (DBG) Log.d(TAG, "Found a node"); 286 BluetoothDevice device = stateMachine.getDevice(); 287 if (device != null) { 288 setActiveDevice(device); 289 } 290 stateMachine.playItem(requestedNode); 291 break; 292 } 293 } 294 } 295 } 296 297 /*Java API*/ 298 299 /** 300 * Get a List of MediaItems that are children of the specified media Id 301 * 302 * @param parentMediaId The player or folder to get the contents of 303 * @return List of Children if available, an empty list if there are none, 304 * or null if a search must be performed. 305 */ getContents(String parentMediaId)306 public synchronized List<MediaItem> getContents(String parentMediaId) { 307 if (DBG) Log.d(TAG, "getContents(" + parentMediaId + ")"); 308 309 BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId); 310 if (requestedNode == null) { 311 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 312 requestedNode = stateMachine.findNode(parentMediaId); 313 if (requestedNode != null) { 314 Log.d(TAG, "Found a node"); 315 break; 316 } 317 } 318 } 319 320 // If we don't find a node in the tree then do not have any way to browse for the contents. 321 // Return an empty list instead. 322 if (requestedNode == null) { 323 if (DBG) Log.d(TAG, "Didn't find a node"); 324 return new ArrayList(0); 325 } else { 326 // If we found a node and it belongs to a device then go ahead and make it active 327 BluetoothDevice device = requestedNode.getDevice(); 328 if (device != null) { 329 setActiveDevice(device); 330 } 331 332 if (!requestedNode.isCached()) { 333 if (DBG) Log.d(TAG, "node is not cached"); 334 refreshContents(requestedNode); 335 } 336 if (DBG) Log.d(TAG, "Returning contents"); 337 return requestedNode.getContents(); 338 } 339 } 340 341 @Override initBinder()342 protected IProfileServiceBinder initBinder() { 343 return new AvrcpControllerServiceBinder(this); 344 } 345 346 //Binder object: Must be static class or memory leak may occur 347 private static class AvrcpControllerServiceBinder extends IBluetoothAvrcpController.Stub 348 implements IProfileServiceBinder { 349 private AvrcpControllerService mService; 350 351 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getService(AttributionSource source)352 private AvrcpControllerService getService(AttributionSource source) { 353 if (!Utils.checkCallerIsSystemOrActiveUser(TAG) 354 || !Utils.checkServiceAvailable(mService, TAG) 355 || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) { 356 return null; 357 } 358 return mService; 359 } 360 AvrcpControllerServiceBinder(AvrcpControllerService service)361 AvrcpControllerServiceBinder(AvrcpControllerService service) { 362 mService = service; 363 } 364 365 @Override cleanup()366 public void cleanup() { 367 mService = null; 368 } 369 370 @Override getConnectedDevices(AttributionSource source)371 public List<BluetoothDevice> getConnectedDevices(AttributionSource source) { 372 AvrcpControllerService service = getService(source); 373 if (service == null) { 374 return new ArrayList<BluetoothDevice>(0); 375 } 376 return service.getConnectedDevices(); 377 } 378 379 @Override getDevicesMatchingConnectionStates(int[] states, AttributionSource source)380 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states, 381 AttributionSource source) { 382 AvrcpControllerService service = getService(source); 383 if (service == null) { 384 return new ArrayList<BluetoothDevice>(0); 385 } 386 return service.getDevicesMatchingConnectionStates(states); 387 } 388 389 @Override getConnectionState(BluetoothDevice device, AttributionSource source)390 public int getConnectionState(BluetoothDevice device, AttributionSource source) { 391 Attributable.setAttributionSource(device, source); 392 AvrcpControllerService service = getService(source); 393 if (service == null) { 394 return BluetoothProfile.STATE_DISCONNECTED; 395 } 396 return service.getConnectionState(device); 397 } 398 399 @Override sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState, AttributionSource source)400 public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState, 401 AttributionSource source) { 402 Attributable.setAttributionSource(device, source); 403 AvrcpControllerService service = getService(source); 404 if (service == null) { 405 return; 406 } 407 Log.w(TAG, "sendGroupNavigationCmd not implemented"); 408 } 409 410 @Override setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings settings, AttributionSource source)411 public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings settings, 412 AttributionSource source) { 413 AvrcpControllerService service = getService(source); 414 if (service == null) { 415 return false; 416 } 417 Log.w(TAG, "setPlayerApplicationSetting not implemented"); 418 return false; 419 } 420 421 @Override getPlayerSettings(BluetoothDevice device, AttributionSource source)422 public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device, 423 AttributionSource source) { 424 Attributable.setAttributionSource(device, source); 425 AvrcpControllerService service = getService(source); 426 if (service == null) { 427 return null; 428 } 429 Log.w(TAG, "getPlayerSettings not implemented"); 430 return null; 431 } 432 } 433 434 435 /* JNI API*/ 436 // Called by JNI when a passthrough key was received. handlePassthroughRsp(int id, int keyState, byte[] address)437 private void handlePassthroughRsp(int id, int keyState, byte[] address) { 438 if (DBG) { 439 Log.d(TAG, "passthrough response received as: key: " + id 440 + " state: " + keyState + "address:" + address); 441 } 442 } 443 handleGroupNavigationRsp(int id, int keyState)444 private void handleGroupNavigationRsp(int id, int keyState) { 445 if (DBG) { 446 Log.d(TAG, "group navigation response received as: key: " + id + " state: " 447 + keyState); 448 } 449 } 450 451 // Called by JNI when a device has connected or disconnected. onConnectionStateChanged(boolean remoteControlConnected, boolean browsingConnected, byte[] address)452 private synchronized void onConnectionStateChanged(boolean remoteControlConnected, 453 boolean browsingConnected, byte[] address) { 454 BluetoothDevice device = getAnonymousDevice(address); 455 if (DBG) { 456 Log.d(TAG, "onConnectionStateChanged " + remoteControlConnected + " " 457 + browsingConnected + device); 458 } 459 if (device == null) { 460 Log.e(TAG, "onConnectionStateChanged Device is null"); 461 return; 462 } 463 464 StackEvent event = 465 StackEvent.connectionStateChanged(remoteControlConnected, browsingConnected); 466 AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device); 467 if (remoteControlConnected || browsingConnected) { 468 stateMachine.connect(event); 469 // The first device to connect gets to be the active device 470 if (getActiveDevice() == null) { 471 setActiveDevice(device); 472 } 473 } else { 474 stateMachine.disconnect(); 475 if (device.equals(getActiveDevice())) { 476 setActiveDevice(null); 477 } 478 } 479 } 480 481 // Called by JNI to notify Avrcp of features supported by the Remote device. getRcFeatures(byte[] address, int features)482 private void getRcFeatures(byte[] address, int features) { 483 /* Do Nothing. */ 484 } 485 486 // Called by JNI to notify Avrcp of a remote device's Cover Art PSM getRcPsm(byte[] address, int psm)487 private void getRcPsm(byte[] address, int psm) { 488 BluetoothDevice device = getAnonymousDevice(address); 489 if (DBG) Log.d(TAG, "getRcPsm(device=" + device + ", psm=" + psm + ")"); 490 AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device); 491 if (stateMachine != null) { 492 stateMachine.sendMessage( 493 AvrcpControllerStateMachine.MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM, psm); 494 } 495 } 496 497 // Called by JNI setPlayerAppSettingRsp(byte[] address, byte accepted)498 private void setPlayerAppSettingRsp(byte[] address, byte accepted) { 499 /* Do Nothing. */ 500 } 501 502 // Called by JNI when remote wants to receive absolute volume notifications. handleRegisterNotificationAbsVol(byte[] address, byte label)503 private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) { 504 if (DBG) { 505 Log.d(TAG, "handleRegisterNotificationAbsVol"); 506 } 507 BluetoothDevice device = getAnonymousDevice(address); 508 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 509 if (stateMachine != null) { 510 stateMachine.sendMessage( 511 AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION); 512 } 513 } 514 515 // Called by JNI when remote wants to set absolute volume. handleSetAbsVolume(byte[] address, byte absVol, byte label)516 private synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) { 517 if (DBG) { 518 Log.d(TAG, "handleSetAbsVolume "); 519 } 520 BluetoothDevice device = getAnonymousDevice(address); 521 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 522 if (stateMachine != null) { 523 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, 524 absVol); 525 } 526 } 527 528 // Called by JNI when a track changes and local AvrcpController is registered for updates. onTrackChanged(byte[] address, byte numAttributes, int[] attributes, String[] attribVals)529 private synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes, 530 String[] attribVals) { 531 if (DBG) { 532 Log.d(TAG, "onTrackChanged"); 533 } 534 535 BluetoothDevice device = getAnonymousDevice(address); 536 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 537 if (stateMachine != null) { 538 AvrcpItem.Builder aib = new AvrcpItem.Builder(); 539 aib.fromAvrcpAttributeArray(attributes, attribVals); 540 aib.setDevice(device); 541 aib.setItemType(AvrcpItem.TYPE_MEDIA); 542 aib.setUuid(UUID.randomUUID().toString()); 543 AvrcpItem item = aib.build(); 544 if (mCoverArtManager != null) { 545 String handle = item.getCoverArtHandle(); 546 if (handle != null) { 547 item.setCoverArtUuid(mCoverArtManager.getUuidForHandle(device, handle)); 548 } 549 } 550 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED, 551 item); 552 } 553 } 554 555 // Called by JNI periodically based upon timer to update play position onPlayPositionChanged(byte[] address, int songLen, int currSongPosition)556 private synchronized void onPlayPositionChanged(byte[] address, int songLen, 557 int currSongPosition) { 558 if (DBG) { 559 Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition); 560 } 561 BluetoothDevice device = getAnonymousDevice(address); 562 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 563 if (stateMachine != null) { 564 stateMachine.sendMessage( 565 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED, 566 songLen, currSongPosition); 567 } 568 } 569 570 // Called by JNI on changes of play status onPlayStatusChanged(byte[] address, byte playStatus)571 private synchronized void onPlayStatusChanged(byte[] address, byte playStatus) { 572 if (DBG) { 573 Log.d(TAG, "onPlayStatusChanged " + playStatus); 574 } 575 BluetoothDevice device = getAnonymousDevice(address); 576 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 577 if (stateMachine != null) { 578 stateMachine.sendMessage( 579 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, 580 toPlaybackStateFromJni(playStatus)); 581 } 582 } 583 584 // Called by JNI to report remote Player's capabilities handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen)585 private synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, 586 int rspLen) { 587 if (DBG) { 588 Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen); 589 } 590 BluetoothDevice device = getAnonymousDevice(address); 591 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 592 if (stateMachine != null) { 593 PlayerApplicationSettings supportedSettings = 594 PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp); 595 stateMachine.sendMessage( 596 AvrcpControllerStateMachine.MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS, 597 supportedSettings); 598 } 599 } 600 onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen)601 private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, 602 int rspLen) { 603 if (DBG) { 604 Log.d(TAG, "onPlayerAppSettingChanged "); 605 } 606 BluetoothDevice device = getAnonymousDevice(address); 607 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 608 if (stateMachine != null) { 609 610 PlayerApplicationSettings currentSettings = 611 PlayerApplicationSettings.makeSettings(playerAttribRsp); 612 stateMachine.sendMessage( 613 AvrcpControllerStateMachine.MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS, 614 currentSettings); 615 } 616 } 617 onAvailablePlayerChanged(byte[] address)618 private void onAvailablePlayerChanged(byte[] address) { 619 if (DBG) { 620 Log.d(TAG," onAvailablePlayerChanged"); 621 } 622 BluetoothDevice device = getAnonymousDevice(address); 623 624 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 625 if (stateMachine != null) { 626 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED); 627 } 628 } 629 630 // Browsing related JNI callbacks. handleGetFolderItemsRsp(byte[] address, int status, AvrcpItem[] items)631 void handleGetFolderItemsRsp(byte[] address, int status, AvrcpItem[] items) { 632 if (DBG) { 633 Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + " items " 634 + items.length + " items."); 635 } 636 637 BluetoothDevice device = getAnonymousDevice(address); 638 List<AvrcpItem> itemsList = new ArrayList<>(); 639 for (AvrcpItem item : items) { 640 if (VDBG) Log.d(TAG, item.toString()); 641 if (mCoverArtManager != null) { 642 String handle = item.getCoverArtHandle(); 643 if (handle != null) { 644 item.setCoverArtUuid(mCoverArtManager.getUuidForHandle(device, handle)); 645 } 646 } 647 itemsList.add(item); 648 } 649 650 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 651 if (stateMachine != null) { 652 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS, 653 itemsList); 654 } 655 } 656 handleGetPlayerItemsRsp(byte[] address, AvrcpPlayer[] items)657 void handleGetPlayerItemsRsp(byte[] address, AvrcpPlayer[] items) { 658 if (DBG) { 659 Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items."); 660 } 661 662 List<AvrcpPlayer> itemsList = new ArrayList<>(); 663 for (AvrcpPlayer item : items) { 664 if (VDBG) Log.d(TAG, "bt player item: " + item); 665 itemsList.add(item); 666 } 667 668 BluetoothDevice device = getAnonymousDevice(address); 669 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 670 if (stateMachine != null) { 671 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS, 672 itemsList); 673 } 674 } 675 676 // JNI Helper functions to convert native objects to java. createFromNativeMediaItem(byte[] address, long uid, int type, String name, int[] attrIds, String[] attrVals)677 AvrcpItem createFromNativeMediaItem(byte[] address, long uid, int type, String name, 678 int[] attrIds, String[] attrVals) { 679 if (VDBG) { 680 Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type: " + type + " name: " + name 681 + " attrids: " + attrIds + " attrVals: " + attrVals); 682 } 683 684 BluetoothDevice device = getAnonymousDevice(address); 685 AvrcpItem.Builder aib = new AvrcpItem.Builder().fromAvrcpAttributeArray(attrIds, attrVals); 686 aib.setDevice(device); 687 aib.setItemType(AvrcpItem.TYPE_MEDIA); 688 aib.setType(type); 689 aib.setUid(uid); 690 aib.setUuid(UUID.randomUUID().toString()); 691 aib.setPlayable(true); 692 AvrcpItem item = aib.build(); 693 return item; 694 } 695 createFromNativeFolderItem(byte[] address, long uid, int type, String name, int playable)696 AvrcpItem createFromNativeFolderItem(byte[] address, long uid, int type, String name, 697 int playable) { 698 if (VDBG) { 699 Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type + " name " 700 + name + " playable " + playable); 701 } 702 703 BluetoothDevice device = getAnonymousDevice(address); 704 AvrcpItem.Builder aib = new AvrcpItem.Builder(); 705 aib.setDevice(device); 706 aib.setItemType(AvrcpItem.TYPE_FOLDER); 707 aib.setType(type); 708 aib.setUid(uid); 709 aib.setUuid(UUID.randomUUID().toString()); 710 aib.setDisplayableName(name); 711 aib.setPlayable(playable == 0x01); 712 aib.setBrowsable(true); 713 return aib.build(); 714 } 715 createFromNativePlayerItem(byte[] address, int id, String name, byte[] transportFlags, int playStatus, int playerType)716 AvrcpPlayer createFromNativePlayerItem(byte[] address, int id, String name, 717 byte[] transportFlags, int playStatus, int playerType) { 718 if (VDBG) { 719 Log.d(TAG, 720 "createFromNativePlayerItem name: " + name + " transportFlags " 721 + transportFlags + " play status " + playStatus + " player type " 722 + playerType); 723 } 724 BluetoothDevice device = getAnonymousDevice(address); 725 AvrcpPlayer.Builder apb = new AvrcpPlayer.Builder(); 726 apb.setDevice(device); 727 apb.setPlayerId(id); 728 apb.setPlayerType(playerType); 729 apb.setSupportedFeatures(transportFlags); 730 apb.setName(name); 731 apb.setPlayStatus(toPlaybackStateFromJni(playStatus)); 732 return apb.build(); 733 } 734 handleChangeFolderRsp(byte[] address, int count)735 private void handleChangeFolderRsp(byte[] address, int count) { 736 if (DBG) { 737 Log.d(TAG, "handleChangeFolderRsp count: " + count); 738 } 739 BluetoothDevice device = getAnonymousDevice(address); 740 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 741 if (stateMachine != null) { 742 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, 743 count); 744 } 745 } 746 handleSetBrowsedPlayerRsp(byte[] address, int items, int depth)747 private void handleSetBrowsedPlayerRsp(byte[] address, int items, int depth) { 748 if (DBG) { 749 Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth); 750 } 751 BluetoothDevice device = getAnonymousDevice(address); 752 753 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 754 if (stateMachine != null) { 755 stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, 756 items, depth); 757 } 758 } 759 handleSetAddressedPlayerRsp(byte[] address, int status)760 private void handleSetAddressedPlayerRsp(byte[] address, int status) { 761 if (DBG) { 762 Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status); 763 } 764 BluetoothDevice device = getAnonymousDevice(address); 765 766 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 767 if (stateMachine != null) { 768 stateMachine.sendMessage( 769 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER); 770 } 771 } 772 handleAddressedPlayerChanged(byte[] address, int id)773 private void handleAddressedPlayerChanged(byte[] address, int id) { 774 if (DBG) { 775 Log.d(TAG, "handleAddressedPlayerChanged id: " + id); 776 } 777 BluetoothDevice device = getAnonymousDevice(address); 778 779 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 780 if (stateMachine != null) { 781 stateMachine.sendMessage( 782 AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, id); 783 } 784 } 785 handleNowPlayingContentChanged(byte[] address)786 private void handleNowPlayingContentChanged(byte[] address) { 787 if (DBG) { 788 Log.d(TAG, "handleNowPlayingContentChanged"); 789 } 790 BluetoothDevice device = getAnonymousDevice(address); 791 792 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 793 if (stateMachine != null) { 794 stateMachine.nowPlayingContentChanged(); 795 } 796 } 797 798 /* Generic Profile Code */ 799 800 /** 801 * Disconnect the given Bluetooth device. 802 * 803 * @return true if disconnect is successful, false otherwise. 804 */ disconnect(BluetoothDevice device)805 public synchronized boolean disconnect(BluetoothDevice device) { 806 if (DBG) { 807 StringBuilder sb = new StringBuilder(); 808 dump(sb); 809 Log.d(TAG, "MAP disconnect device: " + device 810 + ", InstanceMap start state: " + sb.toString()); 811 } 812 AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device); 813 // a map state machine instance doesn't exist. maybe it is already gone? 814 if (stateMachine == null) { 815 return false; 816 } 817 int connectionState = stateMachine.getState(); 818 if (connectionState != BluetoothProfile.STATE_CONNECTED 819 && connectionState != BluetoothProfile.STATE_CONNECTING) { 820 return false; 821 } 822 stateMachine.disconnect(); 823 if (DBG) { 824 StringBuilder sb = new StringBuilder(); 825 dump(sb); 826 Log.d(TAG, "MAP disconnect device: " + device 827 + ", InstanceMap start state: " + sb.toString()); 828 } 829 return true; 830 } 831 832 /** 833 * Remove state machine from device map once it is no longer needed. 834 */ removeStateMachine(AvrcpControllerStateMachine stateMachine)835 public void removeStateMachine(AvrcpControllerStateMachine stateMachine) { 836 BluetoothDevice device = stateMachine.getDevice(); 837 if (device.equals(getActiveDevice())) { 838 setActiveDevice(null); 839 } 840 mDeviceStateMap.remove(stateMachine.getDevice()); 841 } 842 getConnectedDevices()843 public List<BluetoothDevice> getConnectedDevices() { 844 return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED}); 845 } 846 getStateMachine(BluetoothDevice device)847 protected AvrcpControllerStateMachine getStateMachine(BluetoothDevice device) { 848 if (device == null) { 849 return null; 850 } 851 return mDeviceStateMap.get(device); 852 } 853 getOrCreateStateMachine(BluetoothDevice device)854 protected AvrcpControllerStateMachine getOrCreateStateMachine(BluetoothDevice device) { 855 AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device); 856 if (stateMachine == null) { 857 stateMachine = newStateMachine(device); 858 mDeviceStateMap.put(device, stateMachine); 859 stateMachine.start(); 860 } 861 return stateMachine; 862 } 863 getCoverArtManager()864 protected AvrcpCoverArtManager getCoverArtManager() { 865 return mCoverArtManager; 866 } 867 getDevicesMatchingConnectionStates(int[] states)868 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 869 if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states)); 870 List<BluetoothDevice> deviceList = new ArrayList<>(); 871 BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 872 int connectionState; 873 for (BluetoothDevice device : bondedDevices) { 874 connectionState = getConnectionState(device); 875 if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState); 876 for (int i = 0; i < states.length; i++) { 877 if (connectionState == states[i]) { 878 deviceList.add(device); 879 } 880 } 881 } 882 if (DBG) Log.d(TAG, deviceList.toString()); 883 Log.d(TAG, "GetDevicesDone"); 884 return deviceList; 885 } 886 getConnectionState(BluetoothDevice device)887 synchronized int getConnectionState(BluetoothDevice device) { 888 AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device); 889 return (stateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED 890 : stateMachine.getState(); 891 } 892 893 @Override dump(StringBuilder sb)894 public void dump(StringBuilder sb) { 895 super.dump(sb); 896 ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size()); 897 ProfileService.println(sb, "Active Device = " + mActiveDevice); 898 899 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 900 ProfileService.println(sb, 901 "==== StateMachine for " + stateMachine.getDevice() + " ===="); 902 stateMachine.dump(sb); 903 } 904 sb.append("\n sBrowseTree: " + sBrowseTree.toString()); 905 906 sb.append("\n Cover Artwork Enabled: " + (mCoverArtEnabled ? "True" : "False")); 907 if (mCoverArtManager != null) { 908 sb.append("\n " + mCoverArtManager.toString()); 909 } 910 911 sb.append("\n " + BluetoothMediaBrowserService.dump() + "\n"); 912 } 913 914 /*JNI*/ classInitNative()915 private static native void classInitNative(); 916 initNative()917 private native void initNative(); 918 cleanupNative()919 private native void cleanupNative(); 920 921 /** 922 * Send button press commands to addressed device 923 * 924 * @param keyCode key code as defined in AVRCP specification 925 * @param keyState 0 = key pressed, 1 = key released 926 * @return command was sent 927 */ sendPassThroughCommandNative(byte[] address, int keyCode, int keyState)928 public native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState); 929 930 /** 931 * Send group navigation commands 932 * 933 * @param keyCode next/previous 934 * @param keyState state 935 * @return command was sent 936 */ sendGroupNavigationCommandNative(byte[] address, int keyCode, int keyState)937 public native boolean sendGroupNavigationCommandNative(byte[] address, int keyCode, 938 int keyState); 939 940 /** 941 * Change player specific settings such as shuffle 942 * 943 * @param numAttrib number of settings being sent 944 * @param attribIds list of settings to be changed 945 * @param attribVal list of settings values 946 */ setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib, byte[] attribIds, byte[] attribVal)947 public native void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib, 948 byte[] attribIds, byte[] attribVal); 949 950 /** 951 * Send response to set absolute volume 952 * 953 * @param absVol new volume 954 * @param label label 955 */ sendAbsVolRspNative(byte[] address, int absVol, int label)956 public native void sendAbsVolRspNative(byte[] address, int absVol, int label); 957 958 /** 959 * Register for any volume level changes 960 * 961 * @param rspType type of response 962 * @param absVol current volume 963 * @param label label 964 */ sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol, int label)965 public native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol, 966 int label); 967 968 /** 969 * Fetch the current track's metadata 970 * 971 * This method is specifically meant to allow us to fetch image handles that may not have been 972 * sent to us yet, prior to having a BIP client connection. See the AVRCP 1.6+ specification, 973 * section 4.1.7, for more details. 974 */ getCurrentMetadataNative(byte[] address)975 public native void getCurrentMetadataNative(byte[] address); 976 977 /** 978 * Fetch the playback state 979 */ getPlaybackStateNative(byte[] address)980 public native void getPlaybackStateNative(byte[] address); 981 982 /** 983 * Fetch the current now playing list 984 * 985 * @param start first index to retrieve 986 * @param end last index to retrieve 987 */ getNowPlayingListNative(byte[] address, int start, int end)988 public native void getNowPlayingListNative(byte[] address, int start, int end); 989 990 /** 991 * Fetch the current folder's listing 992 * 993 * @param start first index to retrieve 994 * @param end last index to retrieve 995 */ getFolderListNative(byte[] address, int start, int end)996 public native void getFolderListNative(byte[] address, int start, int end); 997 998 /** 999 * Fetch the listing of players 1000 * 1001 * @param start first index to retrieve 1002 * @param end last index to retrieve 1003 */ getPlayerListNative(byte[] address, int start, int end)1004 public native void getPlayerListNative(byte[] address, int start, int end); 1005 1006 /** 1007 * Change the current browsed folder 1008 * 1009 * @param direction up/down 1010 * @param uid folder unique id 1011 */ changeFolderPathNative(byte[] address, byte direction, long uid)1012 public native void changeFolderPathNative(byte[] address, byte direction, long uid); 1013 1014 /** 1015 * Play item with provided uid 1016 * 1017 * @param scope scope of item to played 1018 * @param uid song unique id 1019 * @param uidCounter counter 1020 */ playItemNative(byte[] address, byte scope, long uid, int uidCounter)1021 public native void playItemNative(byte[] address, byte scope, long uid, int uidCounter); 1022 1023 /** 1024 * Set a specific player for browsing 1025 * 1026 * @param playerId player number 1027 */ setBrowsedPlayerNative(byte[] address, int playerId)1028 public native void setBrowsedPlayerNative(byte[] address, int playerId); 1029 1030 /** 1031 * Set a specific player for handling playback commands 1032 * 1033 * @param playerId player number 1034 */ setAddressedPlayerNative(byte[] address, int playerId)1035 public native void setAddressedPlayerNative(byte[] address, int playerId); 1036 } 1037