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