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