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 android.bluetooth.BluetoothAvrcpController; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothProfile; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.media.AudioManager; 25 import android.media.MediaMetadata; 26 import android.media.browse.MediaBrowser.MediaItem; 27 import android.media.session.MediaSession; 28 import android.media.session.PlaybackState; 29 import android.os.Bundle; 30 import android.os.Message; 31 import android.util.Log; 32 import android.util.SparseArray; 33 34 import com.android.bluetooth.BluetoothMetricsProto; 35 import com.android.bluetooth.R; 36 import com.android.bluetooth.Utils; 37 import com.android.bluetooth.a2dpsink.A2dpSinkService; 38 import com.android.bluetooth.btservice.MetricsLogger; 39 import com.android.bluetooth.btservice.ProfileService; 40 import com.android.internal.util.State; 41 import com.android.internal.util.StateMachine; 42 43 import java.util.ArrayList; 44 import java.util.List; 45 /** 46 * Provides Bluetooth AVRCP Controller State Machine responsible for all remote control connections 47 * and interactions with a remote controlable device. 48 */ 49 class AvrcpControllerStateMachine extends StateMachine { 50 static final String TAG = "AvrcpControllerStateMachine"; 51 static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 52 53 //0->99 Events from Outside 54 public static final int CONNECT = 1; 55 public static final int DISCONNECT = 2; 56 57 //100->199 Internal Events 58 protected static final int CLEANUP = 100; 59 private static final int CONNECT_TIMEOUT = 101; 60 61 //200->299 Events from Native 62 static final int STACK_EVENT = 200; 63 static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 201; 64 65 static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 203; 66 static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 204; 67 static final int MESSAGE_PROCESS_TRACK_CHANGED = 205; 68 static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 206; 69 static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 207; 70 static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 208; 71 static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 209; 72 static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE = 210; 73 static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 211; 74 static final int MESSAGE_PROCESS_FOLDER_PATH = 212; 75 static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 213; 76 static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 214; 77 static final int MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED = 215; 78 static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 216; 79 80 //300->399 Events for Browsing 81 static final int MESSAGE_GET_FOLDER_ITEMS = 300; 82 static final int MESSAGE_PLAY_ITEM = 301; 83 static final int MSG_AVRCP_PASSTHRU = 302; 84 85 static final int MESSAGE_INTERNAL_ABS_VOL_TIMEOUT = 404; 86 87 /* 88 * Base value for absolute volume from JNI 89 */ 90 private static final int ABS_VOL_BASE = 127; 91 92 private final AudioManager mAudioManager; 93 94 protected final BluetoothDevice mDevice; 95 protected final byte[] mDeviceAddress; 96 protected final AvrcpControllerService mService; 97 protected final Disconnected mDisconnected; 98 protected final Connecting mConnecting; 99 protected final Connected mConnected; 100 protected final Disconnecting mDisconnecting; 101 102 protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED; 103 104 boolean mRemoteControlConnected = false; 105 boolean mBrowsingConnected = false; 106 final BrowseTree mBrowseTree; 107 private AvrcpPlayer mAddressedPlayer = new AvrcpPlayer(); 108 private int mAddressedPlayerId = -1; 109 private SparseArray<AvrcpPlayer> mAvailablePlayerList = new SparseArray<AvrcpPlayer>(); 110 private int mVolumeChangedNotificationsToIgnore = 0; 111 112 GetFolderList mGetFolderList = null; 113 114 //Number of items to get in a single fetch 115 static final int ITEM_PAGE_SIZE = 20; 116 static final int CMD_TIMEOUT_MILLIS = 10000; 117 static final int ABS_VOL_TIMEOUT_MILLIS = 1000; //1s 118 AvrcpControllerStateMachine(BluetoothDevice device, AvrcpControllerService service)119 AvrcpControllerStateMachine(BluetoothDevice device, AvrcpControllerService service) { 120 super(TAG); 121 mDevice = device; 122 mDeviceAddress = Utils.getByteAddress(mDevice); 123 mService = service; 124 logD(device.toString()); 125 126 mBrowseTree = new BrowseTree(mDevice); 127 mDisconnected = new Disconnected(); 128 mConnecting = new Connecting(); 129 mConnected = new Connected(); 130 mDisconnecting = new Disconnecting(); 131 132 addState(mDisconnected); 133 addState(mConnecting); 134 addState(mConnected); 135 addState(mDisconnecting); 136 137 mGetFolderList = new GetFolderList(); 138 addState(mGetFolderList, mConnected); 139 mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE); 140 141 setInitialState(mDisconnected); 142 } 143 findNode(String parentMediaId)144 BrowseTree.BrowseNode findNode(String parentMediaId) { 145 logD("FindNode"); 146 return mBrowseTree.findBrowseNodeByID(parentMediaId); 147 } 148 149 /** 150 * Get the current connection state 151 * 152 * @return current State 153 */ getState()154 public int getState() { 155 return mMostRecentState; 156 } 157 158 /** 159 * Get the underlying device tracked by this state machine 160 * 161 * @return device in focus 162 */ getDevice()163 public synchronized BluetoothDevice getDevice() { 164 return mDevice; 165 } 166 167 /** 168 * send the connection event asynchronously 169 */ connect(StackEvent event)170 public boolean connect(StackEvent event) { 171 if (event.mBrowsingConnected) { 172 onBrowsingConnected(); 173 } 174 mRemoteControlConnected = event.mRemoteControlConnected; 175 sendMessage(CONNECT); 176 return true; 177 } 178 179 /** 180 * send the Disconnect command asynchronously 181 */ disconnect()182 public void disconnect() { 183 sendMessage(DISCONNECT); 184 } 185 186 /** 187 * Dump the current State Machine to the string builder. 188 * 189 * @param sb output string 190 */ dump(StringBuilder sb)191 public void dump(StringBuilder sb) { 192 ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "(" 193 + mDevice.getName() + ") " + this.toString()); 194 } 195 196 @Override unhandledMessage(Message msg)197 protected void unhandledMessage(Message msg) { 198 Log.w(TAG, "Unhandled message in state " + getCurrentState() + "msg.what=" + msg.what); 199 } 200 logD(String message)201 private static void logD(String message) { 202 if (DBG) { 203 Log.d(TAG, message); 204 } 205 } 206 onBrowsingConnected()207 synchronized void onBrowsingConnected() { 208 if (mBrowsingConnected) return; 209 mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode); 210 BluetoothMediaBrowserService.notifyChanged(mService 211 .sBrowseTree.mRootNode); 212 BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); 213 mBrowsingConnected = true; 214 } 215 onBrowsingDisconnected()216 synchronized void onBrowsingDisconnected() { 217 if (!mBrowsingConnected) return; 218 mAddressedPlayer.setPlayStatus(PlaybackState.STATE_ERROR); 219 mAddressedPlayer.updateCurrentTrack(null); 220 mBrowseTree.mNowPlayingNode.setCached(false); 221 BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode); 222 PlaybackState.Builder pbb = new PlaybackState.Builder(); 223 pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN, 224 1.0f).setActions(0); 225 pbb.setErrorMessage(mService.getString(R.string.bluetooth_disconnected)); 226 BluetoothMediaBrowserService.notifyChanged(pbb.build()); 227 mService.sBrowseTree.mRootNode.removeChild( 228 mBrowseTree.mRootNode); 229 BluetoothMediaBrowserService.notifyChanged(mService 230 .sBrowseTree.mRootNode); 231 BluetoothMediaBrowserService.trackChanged(null); 232 mBrowsingConnected = false; 233 } 234 notifyChanged(BrowseTree.BrowseNode node)235 private void notifyChanged(BrowseTree.BrowseNode node) { 236 BluetoothMediaBrowserService.notifyChanged(node); 237 } 238 requestContents(BrowseTree.BrowseNode node)239 void requestContents(BrowseTree.BrowseNode node) { 240 sendMessage(MESSAGE_GET_FOLDER_ITEMS, node); 241 242 logD("Fetching " + node); 243 } 244 nowPlayingContentChanged()245 void nowPlayingContentChanged() { 246 mBrowseTree.mNowPlayingNode.setCached(false); 247 sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode); 248 } 249 250 protected class Disconnected extends State { 251 @Override enter()252 public void enter() { 253 logD("Enter Disconnected"); 254 if (mMostRecentState != BluetoothProfile.STATE_DISCONNECTED) { 255 sendMessage(CLEANUP); 256 } 257 broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTED); 258 } 259 260 @Override processMessage(Message message)261 public boolean processMessage(Message message) { 262 switch (message.what) { 263 case CONNECT: 264 logD("Connect"); 265 transitionTo(mConnecting); 266 break; 267 case CLEANUP: 268 mService.removeStateMachine(AvrcpControllerStateMachine.this); 269 break; 270 } 271 return true; 272 } 273 } 274 275 protected class Connecting extends State { 276 @Override enter()277 public void enter() { 278 logD("Enter Connecting"); 279 broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTING); 280 transitionTo(mConnected); 281 } 282 } 283 284 285 class Connected extends State { 286 private static final String STATE_TAG = "Avrcp.ConnectedAvrcpController"; 287 private int mCurrentlyHeldKey = 0; 288 289 @Override enter()290 public void enter() { 291 if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) { 292 broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED); 293 BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks); 294 } else { 295 logD("ReEnteringConnected"); 296 } 297 super.enter(); 298 } 299 300 @Override processMessage(Message msg)301 public boolean processMessage(Message msg) { 302 logD(STATE_TAG + " processMessage " + msg.what); 303 switch (msg.what) { 304 case MESSAGE_PROCESS_SET_ABS_VOL_CMD: 305 mVolumeChangedNotificationsToIgnore++; 306 removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT); 307 sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT, 308 ABS_VOL_TIMEOUT_MILLIS); 309 setAbsVolume(msg.arg1, msg.arg2); 310 return true; 311 312 case MESSAGE_GET_FOLDER_ITEMS: 313 transitionTo(mGetFolderList); 314 return true; 315 316 case MESSAGE_PLAY_ITEM: 317 //Set Addressed Player 318 playItem((BrowseTree.BrowseNode) msg.obj); 319 return true; 320 321 case MSG_AVRCP_PASSTHRU: 322 passThru(msg.arg1); 323 return true; 324 325 case MESSAGE_PROCESS_TRACK_CHANGED: 326 mAddressedPlayer.updateCurrentTrack((MediaMetadata) msg.obj); 327 BluetoothMediaBrowserService.trackChanged((MediaMetadata) msg.obj); 328 return true; 329 330 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: 331 mAddressedPlayer.setPlayStatus(msg.arg1); 332 BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState()); 333 if (mAddressedPlayer.getPlaybackState().getState() 334 == PlaybackState.STATE_PLAYING 335 && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE 336 && !shouldRequestFocus()) { 337 sendMessage(MSG_AVRCP_PASSTHRU, 338 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); 339 } 340 return true; 341 342 case MESSAGE_PROCESS_PLAY_POS_CHANGED: 343 if (msg.arg2 != -1) { 344 mAddressedPlayer.setPlayTime(msg.arg2); 345 346 BluetoothMediaBrowserService.notifyChanged( 347 mAddressedPlayer.getPlaybackState()); 348 } 349 return true; 350 351 case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED: 352 mAddressedPlayerId = msg.arg1; 353 logD("AddressedPlayer = " + mAddressedPlayerId); 354 AvrcpPlayer updatedPlayer = mAvailablePlayerList.get(mAddressedPlayerId); 355 if (updatedPlayer != null) { 356 mAddressedPlayer = updatedPlayer; 357 logD("AddressedPlayer = " + mAddressedPlayer.getName()); 358 } else { 359 mBrowseTree.mRootNode.setCached(false); 360 mBrowseTree.mRootNode.setExpectedChildren(255); 361 BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode); 362 } 363 return true; 364 365 case DISCONNECT: 366 transitionTo(mDisconnecting); 367 return true; 368 369 default: 370 return super.processMessage(msg); 371 } 372 373 } 374 playItem(BrowseTree.BrowseNode node)375 private void playItem(BrowseTree.BrowseNode node) { 376 if (node == null) { 377 Log.w(TAG, "Invalid item to play"); 378 } else { 379 mService.playItemNative( 380 mDeviceAddress, node.getScope(), 381 node.getBluetoothID(), 0); 382 } 383 } 384 passThru(int cmd)385 private synchronized void passThru(int cmd) { 386 logD("msgPassThru " + cmd); 387 // Some keys should be held until the next event. 388 if (mCurrentlyHeldKey != 0) { 389 mService.sendPassThroughCommandNative( 390 mDeviceAddress, mCurrentlyHeldKey, 391 AvrcpControllerService.KEY_STATE_RELEASED); 392 393 if (mCurrentlyHeldKey == cmd) { 394 // Return to prevent starting FF/FR operation again 395 mCurrentlyHeldKey = 0; 396 return; 397 } else { 398 // FF/FR is in progress and other operation is desired 399 // so after stopping FF/FR, not returning so that command 400 // can be sent for the desired operation. 401 mCurrentlyHeldKey = 0; 402 } 403 } 404 405 // Send the pass through. 406 mService.sendPassThroughCommandNative(mDeviceAddress, cmd, 407 AvrcpControllerService.KEY_STATE_PRESSED); 408 409 if (isHoldableKey(cmd)) { 410 // Release cmd next time a command is sent. 411 mCurrentlyHeldKey = cmd; 412 } else { 413 mService.sendPassThroughCommandNative(mDeviceAddress, 414 cmd, AvrcpControllerService.KEY_STATE_RELEASED); 415 } 416 } 417 isHoldableKey(int cmd)418 private boolean isHoldableKey(int cmd) { 419 return (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_REWIND) 420 || (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_FF); 421 } 422 } 423 424 // Handle the get folder listing action 425 // a) Fetch the listing of folders 426 // b) Once completed return the object listing 427 class GetFolderList extends State { 428 private static final String STATE_TAG = "Avrcp.GetFolderList"; 429 430 boolean mAbort; 431 BrowseTree.BrowseNode mBrowseNode; 432 BrowseTree.BrowseNode mNextStep; 433 434 @Override enter()435 public void enter() { 436 logD(STATE_TAG + " Entering GetFolderList"); 437 // Setup the timeouts. 438 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 439 super.enter(); 440 mAbort = false; 441 Message msg = getCurrentMessage(); 442 if (msg.what == MESSAGE_GET_FOLDER_ITEMS) { 443 { 444 logD(STATE_TAG + " new Get Request"); 445 mBrowseNode = (BrowseTree.BrowseNode) msg.obj; 446 } 447 } 448 449 if (mBrowseNode == null) { 450 transitionTo(mConnected); 451 } else { 452 navigateToFolderOrRetrieve(mBrowseNode); 453 } 454 } 455 456 @Override processMessage(Message msg)457 public boolean processMessage(Message msg) { 458 logD(STATE_TAG + " processMessage " + msg.what); 459 switch (msg.what) { 460 case MESSAGE_PROCESS_GET_FOLDER_ITEMS: 461 ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj; 462 int endIndicator = mBrowseNode.getExpectedChildren() - 1; 463 logD("GetFolderItems: End " + endIndicator 464 + " received " + folderList.size()); 465 466 // Always update the node so that the user does not wait forever 467 // for the list to populate. 468 mBrowseNode.addChildren(folderList); 469 notifyChanged(mBrowseNode); 470 471 if (mBrowseNode.getChildrenCount() >= endIndicator || folderList.size() == 0 472 || mAbort) { 473 // If we have fetched all the elements or if the remotes sends us 0 elements 474 // (which can lead us into a loop since mCurrInd does not proceed) we simply 475 // abort. 476 mBrowseNode.setCached(true); 477 transitionTo(mConnected); 478 } else { 479 // Fetch the next set of items. 480 fetchContents(mBrowseNode); 481 // Reset the timeout message since we are doing a new fetch now. 482 removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); 483 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 484 } 485 break; 486 case MESSAGE_PROCESS_SET_BROWSED_PLAYER: 487 mBrowseTree.setCurrentBrowsedPlayer(mNextStep.getID(), msg.arg1, msg.arg2); 488 removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); 489 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 490 navigateToFolderOrRetrieve(mBrowseNode); 491 break; 492 493 case MESSAGE_PROCESS_FOLDER_PATH: 494 mBrowseTree.setCurrentBrowsedFolder(mNextStep.getID()); 495 mBrowseTree.getCurrentBrowsedFolder().setExpectedChildren(msg.arg1); 496 497 if (mAbort) { 498 transitionTo(mConnected); 499 } else { 500 removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); 501 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 502 navigateToFolderOrRetrieve(mBrowseNode); 503 } 504 break; 505 506 case MESSAGE_PROCESS_GET_PLAYER_ITEMS: 507 BrowseTree.BrowseNode rootNode = mBrowseTree.mRootNode; 508 if (!rootNode.isCached()) { 509 List<AvrcpPlayer> playerList = (List<AvrcpPlayer>) msg.obj; 510 mAvailablePlayerList.clear(); 511 for (AvrcpPlayer player : playerList) { 512 mAvailablePlayerList.put(player.getId(), player); 513 } 514 rootNode.addChildren(playerList); 515 mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT); 516 rootNode.setExpectedChildren(playerList.size()); 517 rootNode.setCached(true); 518 notifyChanged(rootNode); 519 } 520 transitionTo(mConnected); 521 break; 522 523 case MESSAGE_INTERNAL_CMD_TIMEOUT: 524 // We have timed out to execute the request, we should simply send 525 // whatever listing we have gotten until now. 526 Log.w(TAG, "TIMEOUT"); 527 transitionTo(mConnected); 528 break; 529 530 case MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE: 531 // If we have gotten an error for OUT OF RANGE we have 532 // already sent all the items to the client hence simply 533 // transition to Connected state here. 534 mBrowseNode.setCached(true); 535 transitionTo(mConnected); 536 break; 537 538 case MESSAGE_GET_FOLDER_ITEMS: 539 if (!mBrowseNode.equals(msg.obj)) { 540 if (shouldAbort(mBrowseNode.getScope(), 541 ((BrowseTree.BrowseNode) msg.obj).getScope())) { 542 mAbort = true; 543 } 544 deferMessage(msg); 545 logD("GetFolderItems: Go Get Another Directory"); 546 } else { 547 logD("GetFolderItems: Get The Same Directory, ignore"); 548 } 549 break; 550 551 case CONNECT: 552 case DISCONNECT: 553 case MSG_AVRCP_PASSTHRU: 554 case MESSAGE_PROCESS_SET_ABS_VOL_CMD: 555 case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: 556 case MESSAGE_PROCESS_TRACK_CHANGED: 557 case MESSAGE_PROCESS_PLAY_POS_CHANGED: 558 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: 559 case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: 560 case MESSAGE_PLAY_ITEM: 561 case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED: 562 // All of these messages should be handled by parent state immediately. 563 return false; 564 565 default: 566 logD(STATE_TAG + " deferring message " + msg.what 567 + " to connected!"); 568 deferMessage(msg); 569 } 570 return true; 571 } 572 573 /** 574 * shouldAbort calculates the cases where fetching the current directory is no longer 575 * necessary. 576 * 577 * @return true: a new folder in the same scope 578 * a new player while fetching contents of a folder 579 * false: other cases, specifically Now Playing while fetching a folder 580 */ shouldAbort(int currentScope, int fetchScope)581 private boolean shouldAbort(int currentScope, int fetchScope) { 582 if ((currentScope == fetchScope) 583 || (currentScope == AvrcpControllerService.BROWSE_SCOPE_VFS 584 && fetchScope == AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST)) { 585 return true; 586 } 587 return false; 588 } 589 fetchContents(BrowseTree.BrowseNode target)590 private void fetchContents(BrowseTree.BrowseNode target) { 591 int start = target.getChildrenCount(); 592 int end = Math.min(target.getExpectedChildren(), target.getChildrenCount() 593 + ITEM_PAGE_SIZE) - 1; 594 switch (target.getScope()) { 595 case AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST: 596 mService.getPlayerListNative(mDeviceAddress, 597 start, end); 598 break; 599 case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING: 600 mService.getNowPlayingListNative( 601 mDeviceAddress, start, end); 602 break; 603 case AvrcpControllerService.BROWSE_SCOPE_VFS: 604 mService.getFolderListNative(mDeviceAddress, 605 start, end); 606 break; 607 default: 608 Log.e(TAG, STATE_TAG + " Scope " + target.getScope() 609 + " cannot be handled here."); 610 } 611 } 612 613 /* One of several things can happen when trying to get a folder list 614 * 615 * 616 * 0: The folder handle is no longer valid 617 * 1: The folder contents can be retrieved directly (NowPlaying, Root, Current) 618 * 2: The folder is a browsable player 619 * 3: The folder is a non browsable player 620 * 4: The folder is not a child of the current folder 621 * 5: The folder is a child of the current folder 622 * 623 */ navigateToFolderOrRetrieve(BrowseTree.BrowseNode target)624 private void navigateToFolderOrRetrieve(BrowseTree.BrowseNode target) { 625 mNextStep = mBrowseTree.getNextStepToFolder(target); 626 logD("NAVIGATING From " 627 + mBrowseTree.getCurrentBrowsedFolder().toString()); 628 logD("NAVIGATING Toward " + target.toString()); 629 if (mNextStep == null) { 630 return; 631 } else if (target.equals(mBrowseTree.mNowPlayingNode) 632 || target.equals(mBrowseTree.mRootNode) 633 || mNextStep.equals(mBrowseTree.getCurrentBrowsedFolder())) { 634 fetchContents(mNextStep); 635 } else if (mNextStep.isPlayer()) { 636 logD("NAVIGATING Player " + mNextStep.toString()); 637 if (mNextStep.isBrowsable()) { 638 mService.setBrowsedPlayerNative( 639 mDeviceAddress, (int) mNextStep.getBluetoothID()); 640 } else { 641 logD("Player doesn't support browsing"); 642 mNextStep.setCached(true); 643 transitionTo(mConnected); 644 } 645 } else if (mNextStep.equals(mBrowseTree.mNavigateUpNode)) { 646 logD("NAVIGATING UP " + mNextStep.toString()); 647 mNextStep = mBrowseTree.getCurrentBrowsedFolder().getParent(); 648 mBrowseTree.getCurrentBrowsedFolder().setCached(false); 649 650 mService.changeFolderPathNative( 651 mDeviceAddress, 652 AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP, 653 0); 654 655 } else { 656 logD("NAVIGATING DOWN " + mNextStep.toString()); 657 mService.changeFolderPathNative( 658 mDeviceAddress, 659 AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN, 660 mNextStep.getBluetoothID()); 661 } 662 } 663 664 @Override exit()665 public void exit() { 666 removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); 667 mBrowseNode = null; 668 super.exit(); 669 } 670 } 671 672 protected class Disconnecting extends State { 673 @Override enter()674 public void enter() { 675 onBrowsingDisconnected(); 676 broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING); 677 transitionTo(mDisconnected); 678 } 679 } 680 681 setAbsVolume(int absVol, int label)682 private void setAbsVolume(int absVol, int label) { 683 int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 684 int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 685 int newIndex = (maxVolume * absVol) / ABS_VOL_BASE; 686 logD(" setAbsVolume =" + absVol + " maxVol = " + maxVolume 687 + " cur = " + currIndex + " new = " + newIndex); 688 /* 689 * In some cases change in percentage is not sufficient enough to warrant 690 * change in index values which are in range of 0-15. For such cases 691 * no action is required 692 */ 693 if (newIndex != currIndex) { 694 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex, 695 AudioManager.FLAG_SHOW_UI); 696 } 697 mService.sendAbsVolRspNative(mDeviceAddress, absVol, label); 698 } 699 700 MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() { 701 @Override 702 public void onPlay() { 703 logD("onPlay"); 704 onPrepare(); 705 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PLAY); 706 } 707 708 @Override 709 public void onPause() { 710 logD("onPause"); 711 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE); 712 } 713 714 @Override 715 public void onSkipToNext() { 716 logD("onSkipToNext"); 717 onPrepare(); 718 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD); 719 } 720 721 @Override 722 public void onSkipToPrevious() { 723 logD("onSkipToPrevious"); 724 onPrepare(); 725 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD); 726 } 727 728 @Override 729 public void onSkipToQueueItem(long id) { 730 logD("onSkipToQueueItem" + id); 731 onPrepare(); 732 BrowseTree.BrowseNode node = mBrowseTree.getTrackFromNowPlayingList((int) id); 733 if (node != null) { 734 sendMessage(MESSAGE_PLAY_ITEM, node); 735 } 736 } 737 738 @Override 739 public void onStop() { 740 logD("onStop"); 741 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_STOP); 742 } 743 744 @Override 745 public void onPrepare() { 746 logD("onPrepare"); 747 A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService(); 748 if (a2dpSinkService != null) { 749 a2dpSinkService.requestAudioFocus(mDevice, true); 750 } 751 } 752 753 @Override 754 public void onRewind() { 755 logD("onRewind"); 756 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_REWIND); 757 } 758 759 @Override 760 public void onFastForward() { 761 logD("onFastForward"); 762 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FF); 763 } 764 765 @Override 766 public void onPlayFromMediaId(String mediaId, Bundle extras) { 767 logD("onPlayFromMediaId"); 768 // Play the item if possible. 769 onPrepare(); 770 BrowseTree.BrowseNode node = mBrowseTree.findBrowseNodeByID(mediaId); 771 sendMessage(MESSAGE_PLAY_ITEM, node); 772 } 773 }; 774 broadcastConnectionStateChanged(int currentState)775 protected void broadcastConnectionStateChanged(int currentState) { 776 if (mMostRecentState == currentState) { 777 return; 778 } 779 if (currentState == BluetoothProfile.STATE_CONNECTED) { 780 MetricsLogger.logProfileConnectionEvent( 781 BluetoothMetricsProto.ProfileId.AVRCP_CONTROLLER); 782 } 783 logD("Connection state " + mDevice + ": " + mMostRecentState + "->" + currentState); 784 Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED); 785 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, mMostRecentState); 786 intent.putExtra(BluetoothProfile.EXTRA_STATE, currentState); 787 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 788 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 789 mMostRecentState = currentState; 790 mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 791 } 792 shouldRequestFocus()793 private boolean shouldRequestFocus() { 794 return mService.getResources() 795 .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus); 796 } 797 } 798