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