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