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.Manifest.permission.BLUETOOTH_CONNECT; 20 21 import android.bluetooth.BluetoothAvrcpController; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothProfile; 24 import android.content.Intent; 25 import android.media.AudioManager; 26 import android.net.Uri; 27 import android.os.Bundle; 28 import android.os.Message; 29 import android.support.v4.media.MediaBrowserCompat.MediaItem; 30 import android.support.v4.media.session.MediaSessionCompat; 31 import android.support.v4.media.session.PlaybackStateCompat; 32 import android.util.Log; 33 import android.util.SparseArray; 34 35 import com.android.bluetooth.BluetoothMetricsProto; 36 import com.android.bluetooth.R; 37 import com.android.bluetooth.Utils; 38 import com.android.bluetooth.a2dpsink.A2dpSinkService; 39 import com.android.bluetooth.btservice.MetricsLogger; 40 import com.android.bluetooth.btservice.ProfileService; 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.internal.util.State; 43 import com.android.internal.util.StateMachine; 44 45 import java.util.ArrayList; 46 import java.util.List; 47 import java.util.Set; 48 49 /** 50 * Provides Bluetooth AVRCP Controller State Machine responsible for all remote control connections 51 * and interactions with a remote controlable device. 52 */ 53 class AvrcpControllerStateMachine extends StateMachine { 54 static final String TAG = "AvrcpControllerStateMachine"; 55 static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 56 57 //0->99 Events from Outside 58 public static final int CONNECT = 1; 59 public static final int DISCONNECT = 2; 60 public static final int ACTIVE_DEVICE_CHANGE = 3; 61 62 //100->199 Internal Events 63 protected static final int CLEANUP = 100; 64 private static final int CONNECT_TIMEOUT = 101; 65 static final int MESSAGE_INTERNAL_ABS_VOL_TIMEOUT = 102; 66 67 //200->299 Events from Native 68 static final int STACK_EVENT = 200; 69 static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 201; 70 71 static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 203; 72 static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 204; 73 static final int MESSAGE_PROCESS_TRACK_CHANGED = 205; 74 static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 206; 75 static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 207; 76 static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 208; 77 static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 209; 78 static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE = 210; 79 static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 211; 80 static final int MESSAGE_PROCESS_FOLDER_PATH = 212; 81 static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 213; 82 static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 214; 83 static final int MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED = 215; 84 static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 216; 85 static final int MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS = 217; 86 static final int MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS = 218; 87 static final int MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED = 219; 88 static final int MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM = 220; 89 90 //300->399 Events for Browsing 91 static final int MESSAGE_GET_FOLDER_ITEMS = 300; 92 static final int MESSAGE_PLAY_ITEM = 301; 93 static final int MSG_AVRCP_PASSTHRU = 302; 94 static final int MSG_AVRCP_SET_SHUFFLE = 303; 95 static final int MSG_AVRCP_SET_REPEAT = 304; 96 97 //400->499 Events for Cover Artwork 98 static final int MESSAGE_PROCESS_IMAGE_DOWNLOADED = 400; 99 100 /* 101 * Base value for absolute volume from JNI 102 */ 103 private static final int ABS_VOL_BASE = 127; 104 105 /* 106 * Notification types for Avrcp protocol JNI. 107 */ 108 private static final byte NOTIFICATION_RSP_TYPE_INTERIM = 0x00; 109 private static final byte NOTIFICATION_RSP_TYPE_CHANGED = 0x01; 110 111 private static BluetoothDevice sActiveDevice; 112 private final AudioManager mAudioManager; 113 private final boolean mIsVolumeFixed; 114 115 protected final BluetoothDevice mDevice; 116 protected final byte[] mDeviceAddress; 117 protected final AvrcpControllerService mService; 118 protected int mCoverArtPsm; 119 protected final AvrcpCoverArtManager mCoverArtManager; 120 protected final Disconnected mDisconnected; 121 protected final Connecting mConnecting; 122 protected final Connected mConnected; 123 protected final Disconnecting mDisconnecting; 124 125 protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED; 126 127 boolean mRemoteControlConnected = false; 128 boolean mBrowsingConnected = false; 129 final BrowseTree mBrowseTree; 130 131 private AvrcpPlayer mAddressedPlayer; 132 private int mAddressedPlayerId; 133 private SparseArray<AvrcpPlayer> mAvailablePlayerList; 134 135 private int mVolumeChangedNotificationsToIgnore = 0; 136 private int mVolumeNotificationLabel = -1; 137 138 GetFolderList mGetFolderList = null; 139 140 //Number of items to get in a single fetch 141 static final int ITEM_PAGE_SIZE = 20; 142 static final int CMD_TIMEOUT_MILLIS = 10000; 143 static final int ABS_VOL_TIMEOUT_MILLIS = 1000; //1s 144 AvrcpControllerStateMachine(BluetoothDevice device, AvrcpControllerService service)145 AvrcpControllerStateMachine(BluetoothDevice device, AvrcpControllerService service) { 146 super(TAG); 147 mDevice = device; 148 mDeviceAddress = Utils.getByteAddress(mDevice); 149 mService = service; 150 mCoverArtPsm = 0; 151 mCoverArtManager = service.getCoverArtManager(); 152 logD(device.toString()); 153 154 mAvailablePlayerList = new SparseArray<AvrcpPlayer>(); 155 mAddressedPlayerId = AvrcpPlayer.DEFAULT_ID; 156 157 AvrcpPlayer.Builder apb = new AvrcpPlayer.Builder(); 158 apb.setDevice(mDevice); 159 apb.setPlayerId(mAddressedPlayerId); 160 apb.setSupportedFeature(AvrcpPlayer.FEATURE_PLAY); 161 apb.setSupportedFeature(AvrcpPlayer.FEATURE_PAUSE); 162 apb.setSupportedFeature(AvrcpPlayer.FEATURE_STOP); 163 apb.setSupportedFeature(AvrcpPlayer.FEATURE_FORWARD); 164 apb.setSupportedFeature(AvrcpPlayer.FEATURE_PREVIOUS); 165 mAddressedPlayer = apb.build(); 166 mAvailablePlayerList.put(mAddressedPlayerId, mAddressedPlayer); 167 168 mBrowseTree = new BrowseTree(mDevice); 169 mDisconnected = new Disconnected(); 170 mConnecting = new Connecting(); 171 mConnected = new Connected(); 172 mDisconnecting = new Disconnecting(); 173 174 addState(mDisconnected); 175 addState(mConnecting); 176 addState(mConnected); 177 addState(mDisconnecting); 178 179 mGetFolderList = new GetFolderList(); 180 addState(mGetFolderList, mConnected); 181 mAudioManager = service.getSystemService(AudioManager.class); 182 mIsVolumeFixed = mAudioManager.isVolumeFixed(); 183 184 setInitialState(mDisconnected); 185 } 186 findNode(String parentMediaId)187 BrowseTree.BrowseNode findNode(String parentMediaId) { 188 logD("FindNode"); 189 return mBrowseTree.findBrowseNodeByID(parentMediaId); 190 } 191 192 /** 193 * Get the current connection state 194 * 195 * @return current State 196 */ getState()197 public int getState() { 198 return mMostRecentState; 199 } 200 201 /** 202 * Get the underlying device tracked by this state machine 203 * 204 * @return device in focus 205 */ getDevice()206 public BluetoothDevice getDevice() { 207 return mDevice; 208 } 209 210 /** 211 * send the connection event asynchronously 212 */ connect(StackEvent event)213 public boolean connect(StackEvent event) { 214 if (event.mBrowsingConnected) { 215 onBrowsingConnected(); 216 } 217 mRemoteControlConnected = event.mRemoteControlConnected; 218 sendMessage(CONNECT); 219 return true; 220 } 221 222 /** 223 * send the Disconnect command asynchronously 224 */ disconnect()225 public void disconnect() { 226 sendMessage(DISCONNECT); 227 } 228 229 /** 230 * Get the current playing track 231 */ getCurrentTrack()232 public AvrcpItem getCurrentTrack() { 233 return mAddressedPlayer.getCurrentTrack(); 234 } 235 236 @VisibleForTesting getAddressedPlayerId()237 int getAddressedPlayerId() { 238 return mAddressedPlayerId; 239 } 240 241 @VisibleForTesting getAvailablePlayers()242 SparseArray<AvrcpPlayer> getAvailablePlayers() { 243 return mAvailablePlayerList; 244 } 245 246 /** 247 * Dump the current State Machine to the string builder. 248 * 249 * @param sb output string 250 */ dump(StringBuilder sb)251 public void dump(StringBuilder sb) { 252 ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "(" 253 + Utils.getName(mDevice) + ") " + this.toString()); 254 ProfileService.println(sb, "isActive: " + isActive()); 255 ProfileService.println(sb, "Control: " + mRemoteControlConnected); 256 ProfileService.println(sb, "Browsing: " + mBrowsingConnected); 257 ProfileService.println(sb, "Cover Art: " 258 + (mCoverArtManager.getState(mDevice) == BluetoothProfile.STATE_CONNECTED)); 259 260 ProfileService.println(sb, "Addressed Player ID: " + mAddressedPlayerId); 261 ProfileService.println(sb, "Available Players (" + mAvailablePlayerList.size() + "): "); 262 for (int i = 0; i < mAvailablePlayerList.size(); i++) { 263 AvrcpPlayer player = mAvailablePlayerList.valueAt(i); 264 boolean isAddressed = (player.getId() == mAddressedPlayerId); 265 ProfileService.println(sb, "\t" + (isAddressed ? "(Addressed) " : "") + player); 266 } 267 268 List<MediaItem> queue = null; 269 if (mBrowseTree.mNowPlayingNode != null) { 270 queue = mBrowseTree.mNowPlayingNode.getContents(); 271 } 272 ProfileService.println(sb, "Queue (" + (queue == null ? 0 : queue.size()) + "): " + queue); 273 } 274 275 @VisibleForTesting isActive()276 boolean isActive() { 277 return mDevice.equals(mService.getActiveDevice()); 278 } 279 280 /** 281 * Attempt to set the active status for this device 282 */ setDeviceState(int state)283 public void setDeviceState(int state) { 284 sendMessage(ACTIVE_DEVICE_CHANGE, state); 285 } 286 287 @Override unhandledMessage(Message msg)288 protected void unhandledMessage(Message msg) { 289 Log.w(TAG, "Unhandled message in state " + getCurrentState() + "msg.what=" + msg.what); 290 } 291 logD(String message)292 private static void logD(String message) { 293 if (DBG) { 294 Log.d(TAG, message); 295 } 296 } 297 onBrowsingConnected()298 synchronized void onBrowsingConnected() { 299 mBrowsingConnected = true; 300 requestContents(mBrowseTree.mRootNode); 301 } 302 onBrowsingDisconnected()303 synchronized void onBrowsingDisconnected() { 304 if (!mBrowsingConnected) return; 305 mAddressedPlayer.setPlayStatus(PlaybackStateCompat.STATE_ERROR); 306 AvrcpItem previousTrack = mAddressedPlayer.getCurrentTrack(); 307 String previousTrackUuid = previousTrack != null ? previousTrack.getCoverArtUuid() : null; 308 mAddressedPlayer.updateCurrentTrack(null); 309 mBrowseTree.mNowPlayingNode.setCached(false); 310 mBrowseTree.mRootNode.setCached(false); 311 if (isActive()) { 312 BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); 313 BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode); 314 } 315 removeUnusedArtwork(previousTrackUuid); 316 removeUnusedArtworkFromBrowseTree(); 317 mBrowsingConnected = false; 318 } 319 connectCoverArt()320 synchronized void connectCoverArt() { 321 // Called from "connected" state, which assumes either control or browse is connected 322 if (mCoverArtManager != null && mCoverArtPsm != 0 323 && mCoverArtManager.getState(mDevice) != BluetoothProfile.STATE_CONNECTED) { 324 logD("Attempting to connect to AVRCP BIP, psm: " + mCoverArtPsm); 325 mCoverArtManager.connect(mDevice, /* psm */ mCoverArtPsm); 326 } 327 } 328 refreshCoverArt()329 synchronized void refreshCoverArt() { 330 if (mCoverArtManager != null && mCoverArtPsm != 0 331 && mCoverArtManager.getState(mDevice) == BluetoothProfile.STATE_CONNECTED) { 332 logD("Attempting to refresh AVRCP BIP OBEX session, psm: " + mCoverArtPsm); 333 mCoverArtManager.refreshSession(mDevice); 334 } 335 } 336 disconnectCoverArt()337 synchronized void disconnectCoverArt() { 338 // Safe to call even if we're not connected 339 if (mCoverArtManager != null) { 340 logD("Disconnect BIP cover artwork"); 341 mCoverArtManager.disconnect(mDevice); 342 } 343 } 344 345 /** 346 * Remove an unused cover art image from storage if it's unused by the browse tree and the 347 * current track. 348 */ removeUnusedArtwork(String previousTrackUuid)349 synchronized void removeUnusedArtwork(String previousTrackUuid) { 350 logD("removeUnusedArtwork(" + previousTrackUuid + ")"); 351 if (mCoverArtManager == null) return; 352 AvrcpItem currentTrack = getCurrentTrack(); 353 String currentTrackUuid = currentTrack != null ? currentTrack.getCoverArtUuid() : null; 354 if (previousTrackUuid != null) { 355 if (!previousTrackUuid.equals(currentTrackUuid) 356 && mBrowseTree.getNodesUsingCoverArt(previousTrackUuid).isEmpty()) { 357 mCoverArtManager.removeImage(mDevice, previousTrackUuid); 358 } 359 } 360 } 361 362 /** 363 * Queries the browse tree for unused uuids and removes the associated images from storage 364 * if the uuid is not used by the current track. 365 */ removeUnusedArtworkFromBrowseTree()366 synchronized void removeUnusedArtworkFromBrowseTree() { 367 logD("removeUnusedArtworkFromBrowseTree()"); 368 if (mCoverArtManager == null) return; 369 AvrcpItem currentTrack = getCurrentTrack(); 370 String currentTrackUuid = currentTrack != null ? currentTrack.getCoverArtUuid() : null; 371 ArrayList<String> unusedArtwork = mBrowseTree.getAndClearUnusedCoverArt(); 372 for (String uuid : unusedArtwork) { 373 if (!uuid.equals(currentTrackUuid)) { 374 mCoverArtManager.removeImage(mDevice, uuid); 375 } 376 } 377 } 378 notifyChanged(BrowseTree.BrowseNode node)379 private void notifyChanged(BrowseTree.BrowseNode node) { 380 // We should only notify now playing content updates if we're the active device. VFS 381 // updates are fine at any time 382 int scope = node.getScope(); 383 if (scope != AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING 384 || (scope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING 385 && isActive())) { 386 BluetoothMediaBrowserService.notifyChanged(node); 387 } 388 } 389 notifyChanged(PlaybackStateCompat state)390 private void notifyChanged(PlaybackStateCompat state) { 391 if (isActive()) { 392 BluetoothMediaBrowserService.notifyChanged(state); 393 } 394 } 395 requestContents(BrowseTree.BrowseNode node)396 void requestContents(BrowseTree.BrowseNode node) { 397 sendMessage(MESSAGE_GET_FOLDER_ITEMS, node); 398 logD("Fetching " + node); 399 } 400 playItem(BrowseTree.BrowseNode node)401 public void playItem(BrowseTree.BrowseNode node) { 402 sendMessage(MESSAGE_PLAY_ITEM, node); 403 } 404 nowPlayingContentChanged()405 void nowPlayingContentChanged() { 406 mBrowseTree.mNowPlayingNode.setCached(false); 407 removeUnusedArtworkFromBrowseTree(); 408 sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode); 409 } 410 411 protected class Disconnected extends State { 412 @Override enter()413 public void enter() { 414 logD("Enter Disconnected"); 415 if (mMostRecentState != BluetoothProfile.STATE_DISCONNECTED) { 416 sendMessage(CLEANUP); 417 } 418 broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTED); 419 } 420 421 @Override processMessage(Message message)422 public boolean processMessage(Message message) { 423 switch (message.what) { 424 case MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM: 425 mCoverArtPsm = message.arg1; 426 break; 427 case CONNECT: 428 logD("Connect"); 429 transitionTo(mConnecting); 430 break; 431 case CLEANUP: 432 mService.removeStateMachine(AvrcpControllerStateMachine.this); 433 break; 434 case ACTIVE_DEVICE_CHANGE: 435 // Wait until we're connected to process this 436 deferMessage(message); 437 break; 438 } 439 return true; 440 } 441 } 442 443 protected class Connecting extends State { 444 @Override enter()445 public void enter() { 446 logD("Enter Connecting"); 447 broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTING); 448 transitionTo(mConnected); 449 } 450 } 451 452 453 class Connected extends State { 454 private static final String STATE_TAG = "Avrcp.ConnectedAvrcpController"; 455 private int mCurrentlyHeldKey = 0; 456 457 @Override enter()458 public void enter() { 459 if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) { 460 broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED); 461 mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode); 462 BluetoothMediaBrowserService.notifyChanged(mService.sBrowseTree.mRootNode); 463 connectCoverArt(); // only works if we have a valid PSM 464 } else { 465 logD("ReEnteringConnected"); 466 } 467 super.enter(); 468 } 469 470 @Override processMessage(Message msg)471 public boolean processMessage(Message msg) { 472 logD(STATE_TAG + " processMessage " + msg.what); 473 switch (msg.what) { 474 case ACTIVE_DEVICE_CHANGE: 475 int state = msg.arg1; 476 if (state == AvrcpControllerService.DEVICE_STATE_ACTIVE) { 477 BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks); 478 BluetoothMediaBrowserService.trackChanged( 479 mAddressedPlayer.getCurrentTrack()); 480 BluetoothMediaBrowserService.notifyChanged( 481 mAddressedPlayer.getPlaybackState()); 482 BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); 483 } else { 484 sendMessage(MSG_AVRCP_PASSTHRU, 485 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); 486 } 487 return true; 488 489 case MESSAGE_PROCESS_SET_ABS_VOL_CMD: 490 mVolumeChangedNotificationsToIgnore++; 491 removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT); 492 sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT, 493 ABS_VOL_TIMEOUT_MILLIS); 494 handleAbsVolumeRequest(msg.arg1, msg.arg2); 495 return true; 496 497 case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: 498 mVolumeNotificationLabel = msg.arg1; 499 mService.sendRegisterAbsVolRspNative(mDeviceAddress, 500 NOTIFICATION_RSP_TYPE_INTERIM, 501 getAbsVolume(), mVolumeNotificationLabel); 502 return true; 503 504 case MESSAGE_GET_FOLDER_ITEMS: 505 transitionTo(mGetFolderList); 506 return true; 507 508 case MESSAGE_PLAY_ITEM: 509 //Set Addressed Player 510 processPlayItem((BrowseTree.BrowseNode) msg.obj); 511 return true; 512 513 case MSG_AVRCP_PASSTHRU: 514 passThru(msg.arg1); 515 return true; 516 517 case MSG_AVRCP_SET_REPEAT: 518 setRepeat(msg.arg1); 519 return true; 520 521 case MSG_AVRCP_SET_SHUFFLE: 522 setShuffle(msg.arg1); 523 return true; 524 525 case MESSAGE_PROCESS_TRACK_CHANGED: 526 AvrcpItem track = (AvrcpItem) msg.obj; 527 AvrcpItem previousTrack = mAddressedPlayer.getCurrentTrack(); 528 downloadImageIfNeeded(track); 529 mAddressedPlayer.updateCurrentTrack(track); 530 if (isActive()) { 531 BluetoothMediaBrowserService.trackChanged(track); 532 } 533 if (previousTrack != null) { 534 removeUnusedArtwork(previousTrack.getCoverArtUuid()); 535 removeUnusedArtworkFromBrowseTree(); 536 } 537 return true; 538 539 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: 540 mAddressedPlayer.setPlayStatus(msg.arg1); 541 if (!isActive()) { 542 sendMessage(MSG_AVRCP_PASSTHRU, 543 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); 544 return true; 545 } 546 547 PlaybackStateCompat playbackState = mAddressedPlayer.getPlaybackState(); 548 BluetoothMediaBrowserService.notifyChanged(playbackState); 549 550 int focusState = AudioManager.ERROR; 551 A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService(); 552 if (a2dpSinkService != null) { 553 focusState = a2dpSinkService.getFocusState(); 554 } 555 556 if (focusState == AudioManager.ERROR) { 557 sendMessage(MSG_AVRCP_PASSTHRU, 558 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); 559 return true; 560 } 561 562 if (playbackState.getState() == PlaybackStateCompat.STATE_PLAYING 563 && focusState == AudioManager.AUDIOFOCUS_NONE) { 564 if (shouldRequestFocus()) { 565 mSessionCallbacks.onPrepare(); 566 } else { 567 sendMessage(MSG_AVRCP_PASSTHRU, 568 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); 569 } 570 } 571 return true; 572 573 case MESSAGE_PROCESS_PLAY_POS_CHANGED: 574 if (msg.arg2 != -1) { 575 mAddressedPlayer.setPlayTime(msg.arg2); 576 notifyChanged(mAddressedPlayer.getPlaybackState()); 577 } 578 return true; 579 580 case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED: 581 int oldAddressedPlayerId = mAddressedPlayerId; 582 mAddressedPlayerId = msg.arg1; 583 logD("AddressedPlayer changed " + oldAddressedPlayerId + " -> " 584 + mAddressedPlayerId); 585 586 // The now playing list is tied to the addressed player by specification in 587 // AVRCP 5.9.1. A new addressed player means our now playing content is now 588 // invalid 589 mBrowseTree.mNowPlayingNode.setCached(false); 590 if (isActive()) { 591 BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); 592 } 593 removeUnusedArtworkFromBrowseTree(); 594 595 // For devices that support browsing, we *may* have an AvrcpPlayer with player 596 // metadata already. We could also be in the middle fetching it. If the player 597 // isn't there then we need to ensure that a default Addressed AvrcpPlayer is 598 // created to represent it. It can be updated if/when we do fetch the player. 599 if (!mAvailablePlayerList.contains(mAddressedPlayerId)) { 600 logD("Available player set does not contain the new Addressed Player"); 601 AvrcpPlayer.Builder apb = new AvrcpPlayer.Builder(); 602 apb.setDevice(mDevice); 603 apb.setPlayerId(mAddressedPlayerId); 604 apb.setSupportedFeature(AvrcpPlayer.FEATURE_PLAY); 605 apb.setSupportedFeature(AvrcpPlayer.FEATURE_PAUSE); 606 apb.setSupportedFeature(AvrcpPlayer.FEATURE_STOP); 607 apb.setSupportedFeature(AvrcpPlayer.FEATURE_FORWARD); 608 apb.setSupportedFeature(AvrcpPlayer.FEATURE_PREVIOUS); 609 mAvailablePlayerList.put(mAddressedPlayerId, apb.build()); 610 } 611 612 // Set our new addressed player object from our set of available players that's 613 // guaranteed to have the addressed player now. 614 mAddressedPlayer = mAvailablePlayerList.get(mAddressedPlayerId); 615 616 // Fetch metadata including the now playing list if the new player supports the 617 // now playing feature 618 mService.getCurrentMetadataNative(Utils.getByteAddress(mDevice)); 619 mService.getPlaybackStateNative(Utils.getByteAddress(mDevice)); 620 if (mAddressedPlayer.supportsFeature(AvrcpPlayer.FEATURE_NOW_PLAYING)) { 621 sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode); 622 } 623 logD("AddressedPlayer = " + mAddressedPlayer); 624 return true; 625 626 case MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS: 627 mAddressedPlayer.setSupportedPlayerApplicationSettings( 628 (PlayerApplicationSettings) msg.obj); 629 notifyChanged(mAddressedPlayer.getPlaybackState()); 630 return true; 631 632 case MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS: 633 mAddressedPlayer.setCurrentPlayerApplicationSettings( 634 (PlayerApplicationSettings) msg.obj); 635 notifyChanged(mAddressedPlayer.getPlaybackState()); 636 return true; 637 638 case MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED: 639 processAvailablePlayerChanged(); 640 return true; 641 642 case MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM: 643 mCoverArtPsm = msg.arg1; 644 connectCoverArt(); 645 return true; 646 647 case MESSAGE_PROCESS_IMAGE_DOWNLOADED: 648 AvrcpCoverArtManager.DownloadEvent event = 649 (AvrcpCoverArtManager.DownloadEvent) msg.obj; 650 String uuid = event.getUuid(); 651 Uri uri = event.getUri(); 652 logD("Received image for " + uuid + " at " + uri.toString()); 653 654 // Let the addressed player know we got an image so it can see if the current 655 // track now has cover artwork 656 boolean addedArtwork = mAddressedPlayer.notifyImageDownload(uuid, uri); 657 if (addedArtwork && isActive()) { 658 BluetoothMediaBrowserService.trackChanged( 659 mAddressedPlayer.getCurrentTrack()); 660 } 661 662 // Let the browse tree know of the newly downloaded image so it can attach it to 663 // all the items that need it. Notify of changed nodes accordingly 664 Set<BrowseTree.BrowseNode> nodes = mBrowseTree.notifyImageDownload(uuid, uri); 665 for (BrowseTree.BrowseNode node : nodes) { 666 notifyChanged(node); 667 } 668 669 // Delete images that were downloaded and entirely unused 670 if (!addedArtwork && nodes.isEmpty()) { 671 removeUnusedArtwork(uuid); 672 removeUnusedArtworkFromBrowseTree(); 673 } 674 675 return true; 676 677 case DISCONNECT: 678 transitionTo(mDisconnecting); 679 return true; 680 681 default: 682 return super.processMessage(msg); 683 } 684 685 } 686 processPlayItem(BrowseTree.BrowseNode node)687 private void processPlayItem(BrowseTree.BrowseNode node) { 688 if (node == null) { 689 Log.w(TAG, "Invalid item to play"); 690 } else { 691 mService.playItemNative( 692 mDeviceAddress, node.getScope(), 693 node.getBluetoothID(), 0); 694 } 695 } 696 passThru(int cmd)697 private synchronized void passThru(int cmd) { 698 logD("msgPassThru " + cmd); 699 // Some keys should be held until the next event. 700 if (mCurrentlyHeldKey != 0) { 701 mService.sendPassThroughCommandNative( 702 mDeviceAddress, mCurrentlyHeldKey, 703 AvrcpControllerService.KEY_STATE_RELEASED); 704 705 if (mCurrentlyHeldKey == cmd) { 706 // Return to prevent starting FF/FR operation again 707 mCurrentlyHeldKey = 0; 708 return; 709 } else { 710 // FF/FR is in progress and other operation is desired 711 // so after stopping FF/FR, not returning so that command 712 // can be sent for the desired operation. 713 mCurrentlyHeldKey = 0; 714 } 715 } 716 717 // Send the pass through. 718 mService.sendPassThroughCommandNative(mDeviceAddress, cmd, 719 AvrcpControllerService.KEY_STATE_PRESSED); 720 721 if (isHoldableKey(cmd)) { 722 // Release cmd next time a command is sent. 723 mCurrentlyHeldKey = cmd; 724 } else { 725 mService.sendPassThroughCommandNative(mDeviceAddress, 726 cmd, AvrcpControllerService.KEY_STATE_RELEASED); 727 } 728 } 729 isHoldableKey(int cmd)730 private boolean isHoldableKey(int cmd) { 731 return (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_REWIND) 732 || (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_FF); 733 } 734 setRepeat(int repeatMode)735 private void setRepeat(int repeatMode) { 736 mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1, 737 new byte[]{PlayerApplicationSettings.REPEAT_STATUS}, new byte[]{ 738 PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal( 739 PlayerApplicationSettings.REPEAT_STATUS, repeatMode)}); 740 } 741 setShuffle(int shuffleMode)742 private void setShuffle(int shuffleMode) { 743 mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1, 744 new byte[]{PlayerApplicationSettings.SHUFFLE_STATUS}, new byte[]{ 745 PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal( 746 PlayerApplicationSettings.SHUFFLE_STATUS, shuffleMode)}); 747 } 748 processAvailablePlayerChanged()749 private void processAvailablePlayerChanged() { 750 logD("processAvailablePlayerChanged"); 751 mBrowseTree.mRootNode.setCached(false); 752 mBrowseTree.mRootNode.setExpectedChildren(255); 753 BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode); 754 removeUnusedArtworkFromBrowseTree(); 755 requestContents(mBrowseTree.mRootNode); 756 } 757 } 758 759 // Handle the get folder listing action 760 // a) Fetch the listing of folders 761 // b) Once completed return the object listing 762 class GetFolderList extends State { 763 private static final String STATE_TAG = "Avrcp.GetFolderList"; 764 765 boolean mAbort; 766 BrowseTree.BrowseNode mBrowseNode; 767 BrowseTree.BrowseNode mNextStep; 768 769 @Override enter()770 public void enter() { 771 logD(STATE_TAG + " Entering GetFolderList"); 772 // Setup the timeouts. 773 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 774 super.enter(); 775 mAbort = false; 776 Message msg = getCurrentMessage(); 777 if (msg.what == MESSAGE_GET_FOLDER_ITEMS) { 778 { 779 logD(STATE_TAG + " new Get Request"); 780 mBrowseNode = (BrowseTree.BrowseNode) msg.obj; 781 } 782 } 783 784 if (mBrowseNode == null) { 785 transitionTo(mConnected); 786 } else { 787 navigateToFolderOrRetrieve(mBrowseNode); 788 } 789 } 790 791 @Override processMessage(Message msg)792 public boolean processMessage(Message msg) { 793 logD(STATE_TAG + " processMessage " + msg.what); 794 switch (msg.what) { 795 case MESSAGE_PROCESS_GET_FOLDER_ITEMS: 796 ArrayList<AvrcpItem> folderList = (ArrayList<AvrcpItem>) msg.obj; 797 int endIndicator = mBrowseNode.getExpectedChildren() - 1; 798 logD("GetFolderItems: End " + endIndicator 799 + " received " + folderList.size()); 800 801 // Queue up image download if the item has an image and we don't have it yet 802 // Only do this if the feature is enabled. 803 for (AvrcpItem track : folderList) { 804 if (shouldDownloadBrowsedImages()) { 805 downloadImageIfNeeded(track); 806 } else { 807 track.setCoverArtUuid(null); 808 } 809 } 810 811 // Always update the node so that the user does not wait forever 812 // for the list to populate. 813 int newSize = mBrowseNode.addChildren(folderList); 814 logD("Added " + newSize + " items to the browse tree"); 815 notifyChanged(mBrowseNode); 816 817 if (mBrowseNode.getChildrenCount() >= endIndicator || folderList.size() == 0 818 || mAbort) { 819 // If we have fetched all the elements or if the remotes sends us 0 elements 820 // (which can lead us into a loop since mCurrInd does not proceed) we simply 821 // abort. 822 mBrowseNode.setCached(true); 823 transitionTo(mConnected); 824 } else { 825 // Fetch the next set of items. 826 fetchContents(mBrowseNode); 827 // Reset the timeout message since we are doing a new fetch now. 828 removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); 829 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 830 } 831 break; 832 case MESSAGE_PROCESS_SET_BROWSED_PLAYER: 833 mBrowseTree.setCurrentBrowsedPlayer(mNextStep.getID(), msg.arg1, msg.arg2); 834 removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); 835 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 836 navigateToFolderOrRetrieve(mBrowseNode); 837 break; 838 839 case MESSAGE_PROCESS_FOLDER_PATH: 840 mBrowseTree.setCurrentBrowsedFolder(mNextStep.getID()); 841 mBrowseTree.getCurrentBrowsedFolder().setExpectedChildren(msg.arg1); 842 843 // AVRCP Specification says, if we're not database aware, we must disconnect and 844 // reconnect our BIP client each time we successfully change path 845 refreshCoverArt(); 846 847 if (mAbort) { 848 transitionTo(mConnected); 849 } else { 850 removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); 851 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 852 navigateToFolderOrRetrieve(mBrowseNode); 853 } 854 break; 855 856 case MESSAGE_PROCESS_GET_PLAYER_ITEMS: 857 logD("Received new available player items"); 858 BrowseTree.BrowseNode rootNode = mBrowseTree.mRootNode; 859 860 // The specification is not firm on what receiving available player changes 861 // means relative to the existing player IDs, the addressed player and any 862 // currently saved play status, track or now playing list metadata. We're going 863 // to assume nothing and act verbosely, as some devices are known to reuse 864 // Player IDs. 865 if (!rootNode.isCached()) { 866 List<AvrcpPlayer> playerList = (List<AvrcpPlayer>) msg.obj; 867 868 // Since players hold metadata, including cover art handles that point to 869 // stored images, be sure to save image UUIDs so we can see if we can 870 // remove them from storage after setting our new player object 871 ArrayList<String> coverArtUuids = new ArrayList<String>(); 872 for (int i = 0; i < mAvailablePlayerList.size(); i++) { 873 AvrcpPlayer player = mAvailablePlayerList.valueAt(i); 874 AvrcpItem track = player.getCurrentTrack(); 875 if (track != null && track.getCoverArtUuid() != null) { 876 coverArtUuids.add(track.getCoverArtUuid()); 877 } 878 } 879 880 mAvailablePlayerList.clear(); 881 for (AvrcpPlayer player : playerList) { 882 mAvailablePlayerList.put(player.getId(), player); 883 } 884 885 // If our new set of players contains our addressed player again then we 886 // will replace it and re-download metadata. If not, we'll re-use the old 887 // player to save the metadata queries. 888 if (!mAvailablePlayerList.contains(mAddressedPlayerId)) { 889 logD("Available player set doesn't contain the addressed player"); 890 mAvailablePlayerList.put(mAddressedPlayerId, mAddressedPlayer); 891 } else { 892 logD("Update addressed player with new available player metadata"); 893 mAddressedPlayer = mAvailablePlayerList.get(mAddressedPlayerId); 894 mService.getCurrentMetadataNative(Utils.getByteAddress(mDevice)); 895 mService.getPlaybackStateNative(Utils.getByteAddress(mDevice)); 896 mBrowseTree.mNowPlayingNode.setCached(false); 897 if (mAddressedPlayer.supportsFeature(AvrcpPlayer.FEATURE_NOW_PLAYING)) { 898 sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode); 899 } 900 } 901 logD("AddressedPlayer = " + mAddressedPlayer); 902 903 // Check old cover art UUIDs for deletion 904 for (String uuid : coverArtUuids) { 905 removeUnusedArtwork(uuid); 906 } 907 908 // Make sure our browse tree matches our received Available Player set only 909 rootNode.addChildren(playerList); 910 mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT); 911 rootNode.setExpectedChildren(playerList.size()); 912 rootNode.setCached(true); 913 notifyChanged(rootNode); 914 } 915 transitionTo(mConnected); 916 break; 917 918 case MESSAGE_INTERNAL_CMD_TIMEOUT: 919 // We have timed out to execute the request, we should simply send 920 // whatever listing we have gotten until now. 921 Log.w(TAG, "TIMEOUT"); 922 transitionTo(mConnected); 923 break; 924 925 case MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE: 926 // If we have gotten an error for OUT OF RANGE we have 927 // already sent all the items to the client hence simply 928 // transition to Connected state here. 929 mBrowseNode.setCached(true); 930 transitionTo(mConnected); 931 break; 932 933 case MESSAGE_GET_FOLDER_ITEMS: 934 if (!mBrowseNode.equals(msg.obj)) { 935 if (shouldAbort(mBrowseNode.getScope(), 936 ((BrowseTree.BrowseNode) msg.obj).getScope())) { 937 mAbort = true; 938 } 939 deferMessage(msg); 940 logD("GetFolderItems: Go Get Another Directory"); 941 } else { 942 logD("GetFolderItems: Get The Same Directory, ignore"); 943 } 944 break; 945 946 default: 947 // All of these messages should be handled by parent state immediately. 948 return false; 949 } 950 return true; 951 } 952 953 /** 954 * shouldAbort calculates the cases where fetching the current directory is no longer 955 * necessary. 956 * 957 * @return true: a new folder in the same scope 958 * a new player while fetching contents of a folder 959 * false: other cases, specifically Now Playing while fetching a folder 960 */ shouldAbort(int currentScope, int fetchScope)961 private boolean shouldAbort(int currentScope, int fetchScope) { 962 if ((currentScope == fetchScope) 963 || (currentScope == AvrcpControllerService.BROWSE_SCOPE_VFS 964 && fetchScope == AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST)) { 965 return true; 966 } 967 return false; 968 } 969 fetchContents(BrowseTree.BrowseNode target)970 private void fetchContents(BrowseTree.BrowseNode target) { 971 int start = target.getChildrenCount(); 972 int end = Math.min(target.getExpectedChildren(), target.getChildrenCount() 973 + ITEM_PAGE_SIZE) - 1; 974 logD("fetchContents(title=" + target.getID() + ", scope=" + target.getScope() 975 + ", start=" + start + ", end=" + end + ", expected=" 976 + target.getExpectedChildren() + ")"); 977 switch (target.getScope()) { 978 case AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST: 979 mService.getPlayerListNative(mDeviceAddress, 980 start, end); 981 break; 982 case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING: 983 mService.getNowPlayingListNative( 984 mDeviceAddress, start, end); 985 break; 986 case AvrcpControllerService.BROWSE_SCOPE_VFS: 987 mService.getFolderListNative(mDeviceAddress, 988 start, end); 989 break; 990 default: 991 Log.e(TAG, STATE_TAG + " Scope " + target.getScope() 992 + " cannot be handled here."); 993 } 994 } 995 996 /* One of several things can happen when trying to get a folder list 997 * 998 * 999 * 0: The folder handle is no longer valid 1000 * 1: The folder contents can be retrieved directly (NowPlaying, Root, Current) 1001 * 2: The folder is a browsable player 1002 * 3: The folder is a non browsable player 1003 * 4: The folder is not a child of the current folder 1004 * 5: The folder is a child of the current folder 1005 * 1006 */ navigateToFolderOrRetrieve(BrowseTree.BrowseNode target)1007 private void navigateToFolderOrRetrieve(BrowseTree.BrowseNode target) { 1008 mNextStep = mBrowseTree.getNextStepToFolder(target); 1009 logD("NAVIGATING From " 1010 + mBrowseTree.getCurrentBrowsedFolder().toString()); 1011 logD("NAVIGATING Toward " + target.toString()); 1012 if (mNextStep == null) { 1013 return; 1014 } else if (target.equals(mBrowseTree.mNowPlayingNode) 1015 || target.equals(mBrowseTree.mRootNode) 1016 || mNextStep.equals(mBrowseTree.getCurrentBrowsedFolder())) { 1017 fetchContents(mNextStep); 1018 } else if (mNextStep.isPlayer()) { 1019 logD("NAVIGATING Player " + mNextStep.toString()); 1020 if (mNextStep.isBrowsable()) { 1021 mService.setBrowsedPlayerNative( 1022 mDeviceAddress, (int) mNextStep.getBluetoothID()); 1023 } else { 1024 logD("Player doesn't support browsing"); 1025 mNextStep.setCached(true); 1026 transitionTo(mConnected); 1027 } 1028 } else if (mNextStep.equals(mBrowseTree.mNavigateUpNode)) { 1029 logD("NAVIGATING UP " + mNextStep.toString()); 1030 mNextStep = mBrowseTree.getCurrentBrowsedFolder().getParent(); 1031 mBrowseTree.getCurrentBrowsedFolder().setCached(false); 1032 removeUnusedArtworkFromBrowseTree(); 1033 mService.changeFolderPathNative( 1034 mDeviceAddress, 1035 AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP, 1036 0); 1037 1038 } else { 1039 logD("NAVIGATING DOWN " + mNextStep.toString()); 1040 mService.changeFolderPathNative( 1041 mDeviceAddress, 1042 AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN, 1043 mNextStep.getBluetoothID()); 1044 } 1045 } 1046 1047 @Override exit()1048 public void exit() { 1049 removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); 1050 mBrowseNode = null; 1051 super.exit(); 1052 } 1053 } 1054 1055 protected class Disconnecting extends State { 1056 @Override enter()1057 public void enter() { 1058 disconnectCoverArt(); 1059 onBrowsingDisconnected(); 1060 if (mService.sBrowseTree != null) { 1061 mService.sBrowseTree.mRootNode.removeChild(mBrowseTree.mRootNode); 1062 BluetoothMediaBrowserService.notifyChanged(mService.sBrowseTree.mRootNode); 1063 } 1064 broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING); 1065 transitionTo(mDisconnected); 1066 } 1067 } 1068 1069 /** 1070 * Handle a request to align our local volume with the volume of a remote device. If 1071 * we're assuming the source volume is fixed then a response of ABS_VOL_MAX will always be 1072 * sent and no volume adjustment action will be taken on the sink side. 1073 * 1074 * @param absVol A volume level based on a domain of [0, ABS_VOL_MAX] 1075 * @param label Volume notification label 1076 */ handleAbsVolumeRequest(int absVol, int label)1077 private void handleAbsVolumeRequest(int absVol, int label) { 1078 logD("handleAbsVolumeRequest: absVol = " + absVol + ", label = " + label); 1079 if (mIsVolumeFixed) { 1080 logD("Source volume is assumed to be fixed, responding with max volume"); 1081 absVol = ABS_VOL_BASE; 1082 } else { 1083 mVolumeChangedNotificationsToIgnore++; 1084 removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT); 1085 sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT, 1086 ABS_VOL_TIMEOUT_MILLIS); 1087 setAbsVolume(absVol); 1088 } 1089 mService.sendAbsVolRspNative(mDeviceAddress, absVol, label); 1090 } 1091 1092 /** 1093 * Align our volume with a requested absolute volume level 1094 * 1095 * @param absVol A volume level based on a domain of [0, ABS_VOL_MAX] 1096 */ setAbsVolume(int absVol)1097 private void setAbsVolume(int absVol) { 1098 int maxLocalVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 1099 int curLocalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 1100 int reqLocalVolume = (maxLocalVolume * absVol) / ABS_VOL_BASE; 1101 logD("setAbsVolme: absVol = " + absVol + ", reqLocal = " + reqLocalVolume 1102 + ", curLocal = " + curLocalVolume + ", maxLocal = " + maxLocalVolume); 1103 1104 /* 1105 * In some cases change in percentage is not sufficient enough to warrant 1106 * change in index values which are in range of 0-15. For such cases 1107 * no action is required 1108 */ 1109 if (reqLocalVolume != curLocalVolume) { 1110 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, reqLocalVolume, 1111 AudioManager.FLAG_SHOW_UI); 1112 } 1113 } 1114 getAbsVolume()1115 private int getAbsVolume() { 1116 if (mIsVolumeFixed) { 1117 return ABS_VOL_BASE; 1118 } 1119 int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 1120 int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 1121 int newIndex = (currIndex * ABS_VOL_BASE) / maxVolume; 1122 return newIndex; 1123 } 1124 shouldDownloadBrowsedImages()1125 private boolean shouldDownloadBrowsedImages() { 1126 return mService.getResources() 1127 .getBoolean(R.bool.avrcp_controller_cover_art_browsed_images); 1128 } 1129 downloadImageIfNeeded(AvrcpItem track)1130 private void downloadImageIfNeeded(AvrcpItem track) { 1131 if (mCoverArtManager == null) return; 1132 String uuid = track.getCoverArtUuid(); 1133 Uri imageUri = null; 1134 if (uuid != null) { 1135 imageUri = mCoverArtManager.getImageUri(mDevice, uuid); 1136 if (imageUri != null) { 1137 track.setCoverArtLocation(imageUri); 1138 } else { 1139 mCoverArtManager.downloadImage(mDevice, uuid); 1140 } 1141 } 1142 } 1143 1144 MediaSessionCompat.Callback mSessionCallbacks = new MediaSessionCompat.Callback() { 1145 @Override 1146 public void onPlay() { 1147 logD("onPlay"); 1148 onPrepare(); 1149 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PLAY); 1150 } 1151 1152 @Override 1153 public void onPause() { 1154 logD("onPause"); 1155 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); 1156 } 1157 1158 @Override 1159 public void onSkipToNext() { 1160 logD("onSkipToNext"); 1161 onPrepare(); 1162 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD); 1163 } 1164 1165 @Override 1166 public void onSkipToPrevious() { 1167 logD("onSkipToPrevious"); 1168 onPrepare(); 1169 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD); 1170 } 1171 1172 @Override 1173 public void onSkipToQueueItem(long id) { 1174 logD("onSkipToQueueItem id=" + id); 1175 onPrepare(); 1176 BrowseTree.BrowseNode node = mBrowseTree.getTrackFromNowPlayingList((int) id); 1177 if (node != null) { 1178 sendMessage(MESSAGE_PLAY_ITEM, node); 1179 } 1180 } 1181 1182 @Override 1183 public void onStop() { 1184 logD("onStop"); 1185 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_STOP); 1186 } 1187 1188 @Override 1189 public void onPrepare() { 1190 logD("onPrepare"); 1191 A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService(); 1192 if (a2dpSinkService != null) { 1193 a2dpSinkService.requestAudioFocus(mDevice, true); 1194 } 1195 } 1196 1197 @Override 1198 public void onRewind() { 1199 logD("onRewind"); 1200 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_REWIND); 1201 } 1202 1203 @Override 1204 public void onFastForward() { 1205 logD("onFastForward"); 1206 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FF); 1207 } 1208 1209 @Override 1210 public void onPlayFromMediaId(String mediaId, Bundle extras) { 1211 logD("onPlayFromMediaId"); 1212 // Play the item if possible. 1213 onPrepare(); 1214 BrowseTree.BrowseNode node = mBrowseTree.findBrowseNodeByID(mediaId); 1215 if (node != null) { 1216 // node was found on this bluetooth device 1217 sendMessage(MESSAGE_PLAY_ITEM, node); 1218 } else { 1219 // node was not found on this device, pause here, and play on another device 1220 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); 1221 mService.playItem(mediaId); 1222 } 1223 } 1224 1225 @Override 1226 public void onSetRepeatMode(int repeatMode) { 1227 logD("onSetRepeatMode"); 1228 sendMessage(MSG_AVRCP_SET_REPEAT, repeatMode); 1229 } 1230 1231 @Override 1232 public void onSetShuffleMode(int shuffleMode) { 1233 logD("onSetShuffleMode"); 1234 sendMessage(MSG_AVRCP_SET_SHUFFLE, shuffleMode); 1235 1236 } 1237 }; 1238 broadcastConnectionStateChanged(int currentState)1239 protected void broadcastConnectionStateChanged(int currentState) { 1240 if (mMostRecentState == currentState) { 1241 return; 1242 } 1243 if (currentState == BluetoothProfile.STATE_CONNECTED) { 1244 MetricsLogger.logProfileConnectionEvent( 1245 BluetoothMetricsProto.ProfileId.AVRCP_CONTROLLER); 1246 } 1247 logD("Connection state " + mDevice + ": " + mMostRecentState + "->" + currentState); 1248 Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED); 1249 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, mMostRecentState); 1250 intent.putExtra(BluetoothProfile.EXTRA_STATE, currentState); 1251 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 1252 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 1253 mMostRecentState = currentState; 1254 mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); 1255 } 1256 shouldRequestFocus()1257 private boolean shouldRequestFocus() { 1258 return mService.getResources() 1259 .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus); 1260 } 1261 } 1262