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 static android.bluetooth.BluetoothProfile.STATE_CONNECTED; 20 import static android.bluetooth.BluetoothProfile.STATE_CONNECTING; 21 import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED; 22 23 import static java.util.Objects.requireNonNull; 24 25 import android.bluetooth.BluetoothAdapter; 26 import android.bluetooth.BluetoothDevice; 27 import android.content.Intent; 28 import android.media.AudioManager; 29 import android.support.v4.media.MediaBrowserCompat.MediaItem; 30 import android.sysprop.BluetoothProperties; 31 import android.util.Log; 32 33 import com.android.bluetooth.BluetoothPrefs; 34 import com.android.bluetooth.R; 35 import com.android.bluetooth.Utils; 36 import com.android.bluetooth.a2dpsink.A2dpSinkService; 37 import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService.BrowseResult; 38 import com.android.bluetooth.btservice.AdapterService; 39 import com.android.bluetooth.btservice.ProfileService; 40 import com.android.internal.annotations.VisibleForTesting; 41 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.UUID; 47 import java.util.concurrent.ConcurrentHashMap; 48 49 /** Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application. */ 50 public class AvrcpControllerService extends ProfileService { 51 private static final String TAG = AvrcpControllerService.class.getSimpleName(); 52 53 static final int MAXIMUM_CONNECTED_DEVICES = 5; 54 55 /** Owned Components */ 56 private static final String ON_ERROR_SETTINGS_ACTIVITY = 57 BluetoothPrefs.class.getCanonicalName(); 58 59 private static final String COVER_ART_PROVIDER = AvrcpCoverArtProvider.class.getCanonicalName(); 60 61 /* Folder/Media Item scopes. 62 * Keep in sync with AVRCP 1.6 sec. 6.10.1 63 */ 64 public static final byte BROWSE_SCOPE_PLAYER_LIST = 0x00; 65 public static final byte BROWSE_SCOPE_VFS = 0x01; 66 public static final byte BROWSE_SCOPE_SEARCH = 0x02; 67 public static final byte BROWSE_SCOPE_NOW_PLAYING = 0x03; 68 69 /* Folder navigation directions 70 * This is borrowed from AVRCP 1.6 spec and must be kept with same values 71 */ 72 public static final byte FOLDER_NAVIGATION_DIRECTION_UP = 0x00; 73 public static final byte FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01; 74 75 /* 76 * KeyCoded for Pass Through Commands 77 */ 78 public static final int PASS_THRU_CMD_ID_PLAY = 0x44; 79 public static final int PASS_THRU_CMD_ID_PAUSE = 0x46; 80 public static final int PASS_THRU_CMD_ID_VOL_UP = 0x41; 81 public static final int PASS_THRU_CMD_ID_VOL_DOWN = 0x42; 82 public static final int PASS_THRU_CMD_ID_STOP = 0x45; 83 public static final int PASS_THRU_CMD_ID_FF = 0x49; 84 public static final int PASS_THRU_CMD_ID_REWIND = 0x48; 85 public static final int PASS_THRU_CMD_ID_FORWARD = 0x4B; 86 public static final int PASS_THRU_CMD_ID_BACKWARD = 0x4C; 87 88 /* Key State Variables */ 89 public static final int KEY_STATE_PRESSED = 0; 90 public static final int KEY_STATE_RELEASED = 1; 91 92 /* Active Device State Variables */ 93 public static final int DEVICE_STATE_INACTIVE = 0; 94 public static final int DEVICE_STATE_ACTIVE = 1; 95 96 private static AvrcpControllerService sService; 97 98 private final Object mActiveDeviceLock = new Object(); 99 100 private final AdapterService mAdapterService; 101 private final AvrcpControllerNativeInterface mNativeInterface; 102 private final AvrcpCoverArtManager mCoverArtManager; 103 private final boolean mCoverArtEnabled; 104 105 private final BrowseTree mBrowseTree; 106 107 @VisibleForTesting 108 final Map<BluetoothDevice, AvrcpControllerStateMachine> mDeviceStateMap = 109 new ConcurrentHashMap<>(); 110 111 private BluetoothDevice mActiveDevice = null; 112 113 private class ImageDownloadCallback implements AvrcpCoverArtManager.Callback { 114 @Override onImageDownloadComplete( BluetoothDevice device, AvrcpCoverArtManager.DownloadEvent event)115 public void onImageDownloadComplete( 116 BluetoothDevice device, AvrcpCoverArtManager.DownloadEvent event) { 117 Log.d( 118 TAG, 119 "Image downloaded [device: " 120 + device 121 + ", uuid: " 122 + event.uuid() 123 + ", uri: " 124 + event.uri()); 125 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 126 if (stateMachine == null) { 127 Log.e(TAG, "No state machine found for device " + device); 128 mCoverArtManager.removeImage(device, event.uuid()); 129 return; 130 } 131 stateMachine.sendMessage( 132 AvrcpControllerStateMachine.MESSAGE_PROCESS_IMAGE_DOWNLOADED, event); 133 } 134 } 135 AvrcpControllerService(AdapterService adapterService)136 public AvrcpControllerService(AdapterService adapterService) { 137 this(adapterService, AvrcpControllerNativeInterface.getInstance()); 138 } 139 140 @VisibleForTesting AvrcpControllerService( AdapterService adapterService, AvrcpControllerNativeInterface nativeInterface)141 public AvrcpControllerService( 142 AdapterService adapterService, AvrcpControllerNativeInterface nativeInterface) { 143 super(requireNonNull(adapterService)); 144 mAdapterService = adapterService; 145 mNativeInterface = requireNonNull(nativeInterface); 146 mNativeInterface.init(this); 147 148 setComponentAvailable(ON_ERROR_SETTINGS_ACTIVITY, true); 149 mCoverArtEnabled = getResources().getBoolean(R.bool.avrcp_controller_enable_cover_art); 150 if (mCoverArtEnabled) { 151 setComponentAvailable(COVER_ART_PROVIDER, true); 152 mCoverArtManager = new AvrcpCoverArtManager(this, new ImageDownloadCallback()); 153 } else { 154 mCoverArtManager = null; 155 } 156 157 mBrowseTree = new BrowseTree(null); 158 setAvrcpControllerService(this); 159 160 // Start the media browser service. 161 Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class); 162 startService(startIntent); 163 } 164 isEnabled()165 public static boolean isEnabled() { 166 return BluetoothProperties.isProfileAvrcpControllerEnabled().orElse(false); 167 } 168 169 @Override cleanup()170 public synchronized void cleanup() { 171 Log.i(TAG, "Cleanup AVRCP Controller Service"); 172 173 setActiveDevice(null); 174 Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class); 175 stopService(stopIntent); 176 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 177 stateMachine.quitNow(); 178 } 179 mDeviceStateMap.clear(); 180 181 setAvrcpControllerService(null); 182 if (mCoverArtManager != null) { 183 mCoverArtManager.cleanup(); 184 setComponentAvailable(COVER_ART_PROVIDER, false); 185 } 186 setComponentAvailable(ON_ERROR_SETTINGS_ACTIVITY, false); 187 mNativeInterface.cleanup(); 188 } 189 getBrowseTree()190 BrowseTree getBrowseTree() { 191 return mBrowseTree; 192 } 193 getAvrcpControllerService()194 public static synchronized AvrcpControllerService getAvrcpControllerService() { 195 return sService; 196 } 197 198 /** Testing API to inject a mock AvrcpControllerService */ 199 @VisibleForTesting setAvrcpControllerService(AvrcpControllerService service)200 public static synchronized void setAvrcpControllerService(AvrcpControllerService service) { 201 sService = service; 202 } 203 204 /** Get the current active device */ getActiveDevice()205 public BluetoothDevice getActiveDevice() { 206 synchronized (mActiveDeviceLock) { 207 return mActiveDevice; 208 } 209 } 210 211 /** Set the current active device, notify devices of activity status */ 212 @VisibleForTesting setActiveDevice(BluetoothDevice device)213 boolean setActiveDevice(BluetoothDevice device) { 214 Log.d(TAG, "setActiveDevice(device=" + device + ")"); 215 A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService(); 216 if (a2dpSinkService == null) { 217 Log.w(TAG, "setActiveDevice(device=" + device + "): A2DP Sink not available"); 218 return false; 219 } 220 221 BluetoothDevice currentActiveDevice = getActiveDevice(); 222 if ((device == null && currentActiveDevice == null) 223 || (device != null && device.equals(currentActiveDevice))) { 224 return true; 225 } 226 227 // Try and update the active device 228 synchronized (mActiveDeviceLock) { 229 if (a2dpSinkService.setActiveDevice(device)) { 230 mActiveDevice = device; 231 232 // Pause the old active device 233 if (currentActiveDevice != null) { 234 AvrcpControllerStateMachine oldStateMachine = 235 getStateMachine(currentActiveDevice); 236 if (oldStateMachine != null) { 237 oldStateMachine.setDeviceState(DEVICE_STATE_INACTIVE); 238 } 239 } 240 241 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 242 if (stateMachine != null) { 243 stateMachine.setDeviceState(DEVICE_STATE_ACTIVE); 244 } else { 245 BluetoothMediaBrowserService.reset(); 246 } 247 return true; 248 } 249 } 250 251 Log.w(TAG, "setActiveDevice(device=" + device + "): A2DP Sink request failed"); 252 return false; 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 mNativeInterface.getCurrentMetadata(Utils.getByteAddress(device)); 262 } 263 } 264 265 @VisibleForTesting refreshContents(BrowseTree.BrowseNode node)266 void refreshContents(BrowseTree.BrowseNode node) { 267 BluetoothDevice device = node.getDevice(); 268 if (device == null) { 269 return; 270 } 271 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 272 if (stateMachine != null) { 273 stateMachine.requestContents(node); 274 } 275 } 276 playItem(String parentMediaId)277 void playItem(String parentMediaId) { 278 Log.d(TAG, "playItem(" + parentMediaId + ")"); 279 // Check if the requestedNode is a player rather than a song 280 BrowseTree.BrowseNode requestedNode = mBrowseTree.findBrowseNodeByID(parentMediaId); 281 if (requestedNode == null) { 282 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 283 // Check each state machine for the song and then play it 284 requestedNode = stateMachine.findNode(parentMediaId); 285 if (requestedNode != null) { 286 Log.d(TAG, "Found a node, node=" + requestedNode); 287 BluetoothDevice device = stateMachine.getDevice(); 288 if (device != null) { 289 setActiveDevice(device); 290 } 291 stateMachine.playItem(requestedNode); 292 break; 293 } 294 } 295 } 296 } 297 298 /*Java API*/ 299 300 /** 301 * Get a List of MediaItems that are children of the specified media Id 302 * 303 * @param parentMediaId The player or folder to get the contents of 304 * @return List of Children if available, an empty list if there are none, or null if a search 305 * must be performed. 306 */ getContents(String parentMediaId)307 public synchronized BrowseResult getContents(String parentMediaId) { 308 Log.d(TAG, "getContents(" + parentMediaId + ")"); 309 310 BrowseTree.BrowseNode requestedNode = mBrowseTree.findBrowseNodeByID(parentMediaId); 311 if (requestedNode == null) { 312 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 313 requestedNode = stateMachine.findNode(parentMediaId); 314 if (requestedNode != null) { 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 Log.e(TAG, "getContents(" + parentMediaId + "): Failed to find node"); 324 return new BrowseResult(new ArrayList(0), BrowseResult.ERROR_MEDIA_ID_INVALID); 325 } 326 Log.d( 327 TAG, 328 ("getContents(" + parentMediaId + "): ") 329 + ("node=" + requestedNode) 330 + (", device=" + requestedNode.getDevice())); 331 if (parentMediaId.equals(BrowseTree.ROOT) && requestedNode.getChildrenCount() == 0) { 332 return new BrowseResult(null, BrowseResult.NO_DEVICE_CONNECTED); 333 } 334 // If we found a node and it belongs to a device then go ahead and make it active 335 BluetoothDevice device = requestedNode.getDevice(); 336 if (device != null) { 337 setActiveDevice(device); 338 } 339 340 List<MediaItem> contents = requestedNode.getContents(); 341 342 if (!requestedNode.isCached()) { 343 Log.d(TAG, "getContents(" + parentMediaId + "): node download pending"); 344 refreshContents(requestedNode); 345 /* Ongoing downloads can have partial results and we want to make sure they get sent 346 * to the client. If a download gets kicked off as a result of this request, the 347 * contents will be null until the first results arrive. 348 */ 349 return new BrowseResult(contents, BrowseResult.DOWNLOAD_PENDING); 350 } 351 Log.d( 352 TAG, 353 "getContents(" 354 + parentMediaId 355 + "): return node, contents=" 356 + requestedNode.getContents()); 357 return new BrowseResult(contents, BrowseResult.SUCCESS); 358 } 359 360 @Override initBinder()361 protected IProfileServiceBinder initBinder() { 362 return new AvrcpControllerServiceBinder(this); 363 } 364 365 // Called by JNI when a device has connected or disconnected. 366 @VisibleForTesting onConnectionStateChanged( boolean remoteControlConnected, boolean browsingConnected, BluetoothDevice device)367 synchronized void onConnectionStateChanged( 368 boolean remoteControlConnected, boolean browsingConnected, BluetoothDevice device) { 369 StackEvent event = 370 StackEvent.connectionStateChanged(remoteControlConnected, browsingConnected); 371 AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device); 372 if (remoteControlConnected || browsingConnected) { 373 stateMachine.connect(event); 374 // The first device to connect gets to be the active device 375 if (getActiveDevice() == null) { 376 setActiveDevice(device); 377 } 378 } else { 379 stateMachine.disconnect(); 380 if (device.equals(getActiveDevice())) { 381 setActiveDevice(null); 382 } 383 } 384 } 385 386 // Called by JNI to notify Avrcp of a remote device's Cover Art PSM 387 @VisibleForTesting getRcPsm(BluetoothDevice device, int psm)388 void getRcPsm(BluetoothDevice device, int psm) { 389 AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device); 390 stateMachine.sendMessage( 391 AvrcpControllerStateMachine.MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM, psm); 392 } 393 394 // Called by JNI when remote wants to receive absolute volume notifications. 395 @VisibleForTesting handleRegisterNotificationAbsVol(BluetoothDevice device, byte label)396 synchronized void handleRegisterNotificationAbsVol(BluetoothDevice device, byte label) { 397 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 398 if (stateMachine != null) { 399 stateMachine.sendMessage( 400 AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION, 401 label); 402 } 403 } 404 405 // Called by JNI when remote wants to set absolute volume. 406 @VisibleForTesting handleSetAbsVolume(BluetoothDevice device, byte absVol, byte label)407 synchronized void handleSetAbsVolume(BluetoothDevice device, byte absVol, byte label) { 408 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 409 if (stateMachine != null) { 410 stateMachine.sendMessage( 411 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label); 412 } 413 } 414 415 /** 416 * Notify AVRCP Controller of an audio focus state change so we can make requests of the active 417 * player to stop and start playing. 418 */ onAudioFocusStateChanged(int state)419 public void onAudioFocusStateChanged(int state) { 420 Log.d(TAG, "onAudioFocusStateChanged(state=" + state + ")"); 421 422 // Make sure the active device isn't changed while we're processing the event so play/pause 423 // commands get routed to the correct device 424 synchronized (mActiveDeviceLock) { 425 switch (state) { 426 case AudioManager.AUDIOFOCUS_GAIN: 427 BluetoothMediaBrowserService.setActive(true); 428 break; 429 case AudioManager.AUDIOFOCUS_LOSS: 430 BluetoothMediaBrowserService.setActive(false); 431 break; 432 } 433 BluetoothDevice device = getActiveDevice(); 434 if (device == null) { 435 Log.w(TAG, "No active device set, ignore focus change"); 436 return; 437 } 438 439 AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device); 440 if (stateMachine == null) { 441 Log.w(TAG, "No state machine for active device."); 442 return; 443 } 444 stateMachine.sendMessage(AvrcpControllerStateMachine.AUDIO_FOCUS_STATE_CHANGE, state); 445 } 446 } 447 448 // Called by JNI when a track changes and local AvrcpController is registered for updates. 449 @VisibleForTesting onTrackChanged( BluetoothDevice device, byte numAttributes, int[] attributes, String[] attribVals)450 synchronized void onTrackChanged( 451 BluetoothDevice device, byte numAttributes, int[] attributes, String[] attribVals) { 452 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 453 if (stateMachine != null) { 454 AvrcpItem.Builder aib = new AvrcpItem.Builder(); 455 aib.fromAvrcpAttributeArray(attributes, attribVals); 456 aib.setDevice(device); 457 aib.setItemType(AvrcpItem.TYPE_MEDIA); 458 aib.setUuid(UUID.randomUUID().toString()); 459 AvrcpItem item = aib.build(); 460 if (mCoverArtManager != null) { 461 String handle = item.getCoverArtHandle(); 462 if (handle != null) { 463 item.setCoverArtUuid(mCoverArtManager.getUuidForHandle(device, handle)); 464 } 465 } 466 stateMachine.sendMessage( 467 AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED, item); 468 } 469 } 470 471 // Called by JNI periodically based upon timer to update play position 472 @VisibleForTesting onPlayPositionChanged( BluetoothDevice device, int songLen, int currSongPosition)473 synchronized void onPlayPositionChanged( 474 BluetoothDevice device, int songLen, int currSongPosition) { 475 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 476 if (stateMachine != null) { 477 stateMachine.sendMessage( 478 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED, 479 songLen, 480 currSongPosition); 481 } 482 } 483 484 // Called by JNI on changes of play status 485 @VisibleForTesting onPlayStatusChanged(BluetoothDevice device, int playbackState)486 synchronized void onPlayStatusChanged(BluetoothDevice device, int playbackState) { 487 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 488 if (stateMachine != null) { 489 stateMachine.sendMessage( 490 AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState); 491 } 492 } 493 494 // Called by JNI to report remote Player's capabilities 495 @VisibleForTesting handlePlayerAppSetting( BluetoothDevice device, byte[] playerAttribRsp, int rspLen)496 synchronized void handlePlayerAppSetting( 497 BluetoothDevice device, byte[] playerAttribRsp, int rspLen) { 498 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 499 if (stateMachine != null) { 500 PlayerApplicationSettings supportedSettings = 501 PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp); 502 stateMachine.sendMessage( 503 AvrcpControllerStateMachine.MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS, 504 supportedSettings); 505 } 506 } 507 508 @VisibleForTesting onPlayerAppSettingChanged( BluetoothDevice device, byte[] playerAttribRsp, int rspLen)509 synchronized void onPlayerAppSettingChanged( 510 BluetoothDevice device, byte[] playerAttribRsp, int rspLen) { 511 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 512 if (stateMachine != null) { 513 514 PlayerApplicationSettings currentSettings = 515 PlayerApplicationSettings.makeSettings(playerAttribRsp); 516 stateMachine.sendMessage( 517 AvrcpControllerStateMachine.MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS, 518 currentSettings); 519 } 520 } 521 522 @VisibleForTesting onAvailablePlayerChanged(BluetoothDevice device)523 void onAvailablePlayerChanged(BluetoothDevice device) { 524 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 525 if (stateMachine != null) { 526 stateMachine.sendMessage( 527 AvrcpControllerStateMachine.MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED); 528 } 529 } 530 531 // Browsing related JNI callbacks. handleGetFolderItemsRsp(BluetoothDevice device, int status, AvrcpItem[] items)532 void handleGetFolderItemsRsp(BluetoothDevice device, int status, AvrcpItem[] items) { 533 Log.d(TAG, "handleGetFolderItemsRsp(device=" + device + ", status=" + status); 534 List<AvrcpItem> itemsList = new ArrayList<>(); 535 for (AvrcpItem item : items) { 536 Log.v(TAG, "handleGetFolderItemsRsp(device=" + device + "): item=" + item.toString()); 537 if (mCoverArtManager != null) { 538 String handle = item.getCoverArtHandle(); 539 if (handle != null) { 540 item.setCoverArtUuid(mCoverArtManager.getUuidForHandle(device, handle)); 541 } 542 } 543 itemsList.add(item); 544 } 545 546 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 547 if (stateMachine != null) { 548 stateMachine.sendMessage( 549 AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS, itemsList); 550 } 551 } 552 handleGetPlayerItemsRsp(BluetoothDevice device, List<AvrcpPlayer> itemsList)553 void handleGetPlayerItemsRsp(BluetoothDevice device, List<AvrcpPlayer> itemsList) { 554 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 555 if (stateMachine != null) { 556 stateMachine.sendMessage( 557 AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS, itemsList); 558 } 559 } 560 561 @VisibleForTesting handleChangeFolderRsp(BluetoothDevice device, int count)562 void handleChangeFolderRsp(BluetoothDevice device, int count) { 563 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 564 if (stateMachine != null) { 565 stateMachine.sendMessage( 566 AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, count); 567 } 568 } 569 570 @VisibleForTesting handleSetBrowsedPlayerRsp(BluetoothDevice device, int items, int depth)571 void handleSetBrowsedPlayerRsp(BluetoothDevice device, int items, int depth) { 572 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 573 if (stateMachine != null) { 574 stateMachine.sendMessage( 575 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth); 576 } 577 } 578 579 @VisibleForTesting handleSetAddressedPlayerRsp(BluetoothDevice device, int status)580 void handleSetAddressedPlayerRsp(BluetoothDevice device, int status) { 581 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 582 if (stateMachine != null) { 583 stateMachine.sendMessage( 584 AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER); 585 } 586 } 587 588 @VisibleForTesting handleAddressedPlayerChanged(BluetoothDevice device, int id)589 void handleAddressedPlayerChanged(BluetoothDevice device, int id) { 590 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 591 if (stateMachine != null) { 592 stateMachine.sendMessage( 593 AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, id); 594 } 595 } 596 597 @VisibleForTesting handleNowPlayingContentChanged(BluetoothDevice device)598 void handleNowPlayingContentChanged(BluetoothDevice device) { 599 AvrcpControllerStateMachine stateMachine = getStateMachine(device); 600 if (stateMachine != null) { 601 stateMachine.nowPlayingContentChanged(); 602 } 603 } 604 605 /* Generic Profile Code */ 606 607 /** 608 * Disconnect the given Bluetooth device. 609 * 610 * @return true if disconnect is successful, false otherwise. 611 */ disconnect(BluetoothDevice device)612 public synchronized boolean disconnect(BluetoothDevice device) { 613 Log.d(TAG, "disconnect(device=" + device + ")"); 614 AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device); 615 // a map state machine instance doesn't exist. maybe it is already gone? 616 if (stateMachine == null) { 617 return false; 618 } 619 int connectionState = stateMachine.getState(); 620 if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) { 621 return false; 622 } 623 stateMachine.disconnect(); 624 return true; 625 } 626 627 /** Remove state machine from device map once it is no longer needed. */ removeStateMachine(AvrcpControllerStateMachine stateMachine)628 public void removeStateMachine(AvrcpControllerStateMachine stateMachine) { 629 if (stateMachine == null) { 630 return; 631 } 632 BluetoothDevice device = stateMachine.getDevice(); 633 if (device.equals(getActiveDevice())) { 634 setActiveDevice(null); 635 } 636 mDeviceStateMap.remove(stateMachine.getDevice()); 637 stateMachine.quitNow(); 638 } 639 getConnectedDevices()640 public List<BluetoothDevice> getConnectedDevices() { 641 return getDevicesMatchingConnectionStates(new int[] {BluetoothAdapter.STATE_CONNECTED}); 642 } 643 getStateMachine(BluetoothDevice device)644 protected AvrcpControllerStateMachine getStateMachine(BluetoothDevice device) { 645 if (device == null) { 646 return null; 647 } 648 return mDeviceStateMap.get(device); 649 } 650 getOrCreateStateMachine(BluetoothDevice device)651 protected AvrcpControllerStateMachine getOrCreateStateMachine(BluetoothDevice device) { 652 AvrcpControllerStateMachine newStateMachine = 653 new AvrcpControllerStateMachine( 654 mAdapterService, 655 this, 656 device, 657 mNativeInterface, 658 Utils.isAutomotive(getApplicationContext())); 659 AvrcpControllerStateMachine existingStateMachine = 660 mDeviceStateMap.putIfAbsent(device, newStateMachine); 661 // Given null is not a valid value in our map, ConcurrentHashMap will return null if the 662 // key was absent and our new value was added. We should then start and return it. Else 663 // we quit the new one so we don't leak a thread 664 if (existingStateMachine == null) { 665 newStateMachine.start(); 666 return newStateMachine; 667 } else { 668 // If you try to quit a StateMachine that hasn't been constructed yet, the StateMachine 669 // spits out an NPE trying to read a state stack array that only gets made on start(). 670 // We can just quit the thread made explicitly 671 newStateMachine.getHandler().getLooper().quit(); 672 } 673 return existingStateMachine; 674 } 675 getCoverArtManager()676 protected AvrcpCoverArtManager getCoverArtManager() { 677 return mCoverArtManager; 678 } 679 getDevicesMatchingConnectionStates(int[] states)680 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 681 Log.d(TAG, "getDevicesMatchingConnectionStates(states=" + Arrays.toString(states) + ")"); 682 List<BluetoothDevice> deviceList = new ArrayList<>(); 683 BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 684 int connectionState; 685 for (BluetoothDevice device : bondedDevices) { 686 connectionState = getConnectionState(device); 687 for (int i = 0; i < states.length; i++) { 688 if (connectionState == states[i]) { 689 deviceList.add(device); 690 } 691 } 692 } 693 Log.d( 694 TAG, 695 "getDevicesMatchingConnectionStates(states=" 696 + Arrays.toString(states) 697 + "): Found " 698 + deviceList.toString()); 699 return deviceList; 700 } 701 getConnectionState(BluetoothDevice device)702 synchronized int getConnectionState(BluetoothDevice device) { 703 AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device); 704 return (stateMachine == null) ? STATE_DISCONNECTED : stateMachine.getState(); 705 } 706 707 @Override dump(StringBuilder sb)708 public void dump(StringBuilder sb) { 709 super.dump(sb); 710 ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size()); 711 ProfileService.println(sb, "Active Device = " + mActiveDevice); 712 713 for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) { 714 ProfileService.println( 715 sb, "==== StateMachine for " + stateMachine.getDevice() + " ===="); 716 stateMachine.dump(sb); 717 } 718 sb.append("\n BrowseTree:\n"); 719 mBrowseTree.dump(sb); 720 721 sb.append("\n Cover Artwork Enabled: ").append((mCoverArtEnabled ? "True" : "False")); 722 if (mCoverArtManager != null) { 723 sb.append("\n ").append(mCoverArtManager.toString()); 724 } 725 726 sb.append("\n ").append(BluetoothMediaBrowserService.dump()).append("\n"); 727 } 728 } 729