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