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.BluetoothAvrcpPlayerSettings; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothProfile; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.media.AudioManager; 28 import android.media.MediaDescription; 29 import android.media.MediaMetadata; 30 import android.media.browse.MediaBrowser.MediaItem; 31 import android.media.session.PlaybackState; 32 import android.os.Bundle; 33 import android.os.Message; 34 import android.util.Log; 35 36 import com.android.bluetooth.BluetoothMetricsProto; 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.util.State; 42 import com.android.internal.util.StateMachine; 43 44 import java.util.ArrayList; 45 import java.util.List; 46 47 /** 48 * Provides Bluetooth AVRCP Controller State Machine responsible for all remote control connections 49 * and interactions with a remote controlable device. 50 */ 51 class AvrcpControllerStateMachine extends StateMachine { 52 53 // commands from Binder service 54 static final int MESSAGE_SEND_PASS_THROUGH_CMD = 1; 55 static final int MESSAGE_SEND_GROUP_NAVIGATION_CMD = 3; 56 static final int MESSAGE_GET_NOW_PLAYING_LIST = 5; 57 static final int MESSAGE_GET_FOLDER_LIST = 6; 58 static final int MESSAGE_GET_PLAYER_LIST = 7; 59 static final int MESSAGE_CHANGE_FOLDER_PATH = 8; 60 static final int MESSAGE_FETCH_ATTR_AND_PLAY_ITEM = 9; 61 static final int MESSAGE_SET_BROWSED_PLAYER = 10; 62 63 // commands from native layer 64 static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 103; 65 static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 104; 66 static final int MESSAGE_PROCESS_TRACK_CHANGED = 105; 67 static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 106; 68 static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 107; 69 static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 108; 70 static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 109; 71 static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE = 110; 72 static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 111; 73 static final int MESSAGE_PROCESS_FOLDER_PATH = 112; 74 static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 113; 75 static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 114; 76 77 // commands from A2DP sink 78 static final int MESSAGE_STOP_METADATA_BROADCASTS = 201; 79 static final int MESSAGE_START_METADATA_BROADCASTS = 202; 80 81 // commands for connection 82 static final int MESSAGE_PROCESS_RC_FEATURES = 301; 83 static final int MESSAGE_PROCESS_CONNECTION_CHANGE = 302; 84 static final int MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE = 303; 85 86 // Interal messages 87 static final int MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT = 401; 88 static final int MESSAGE_INTERNAL_MOVE_N_LEVELS_UP = 402; 89 static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 403; 90 static final int MESSAGE_INTERNAL_ABS_VOL_TIMEOUT = 404; 91 92 static final int ABS_VOL_TIMEOUT_MILLIS = 1000; //1s 93 static final int CMD_TIMEOUT_MILLIS = 5000; // 5s 94 // Fetch only 20 items at a time. 95 static final int GET_FOLDER_ITEMS_PAGINATION_SIZE = 20; 96 // Fetch no more than 1000 items per directory. 97 static final int MAX_FOLDER_ITEMS = 1000; 98 99 /* 100 * Base value for absolute volume from JNI 101 */ 102 private static final int ABS_VOL_BASE = 127; 103 104 /* 105 * Notification types for Avrcp protocol JNI. 106 */ 107 private static final byte NOTIFICATION_RSP_TYPE_INTERIM = 0x00; 108 private static final byte NOTIFICATION_RSP_TYPE_CHANGED = 0x01; 109 110 111 private static final String TAG = "AvrcpControllerSM"; 112 private static final boolean DBG = true; 113 private static final boolean VDBG = false; 114 115 private final Context mContext; 116 private final AudioManager mAudioManager; 117 118 private final State mDisconnected; 119 private final State mConnected; 120 private final SetBrowsedPlayer mSetBrowsedPlayer; 121 private final SetAddresedPlayerAndPlayItem mSetAddrPlayer; 122 private final ChangeFolderPath mChangeFolderPath; 123 private final GetFolderList mGetFolderList; 124 private final GetPlayerListing mGetPlayerListing; 125 private final MoveToRoot mMoveToRoot; 126 127 private final Object mLock = new Object(); 128 private static final ArrayList<MediaItem> EMPTY_MEDIA_ITEM_LIST = new ArrayList<>(); 129 private static final MediaMetadata EMPTY_MEDIA_METADATA = new MediaMetadata.Builder().build(); 130 131 // APIs exist to access these so they must be thread safe 132 private Boolean mIsConnected = false; 133 private RemoteDevice mRemoteDevice; 134 private AvrcpPlayer mAddressedPlayer; 135 136 // Only accessed from State Machine processMessage 137 private int mVolumeChangedNotificationsToIgnore = 0; 138 private int mPreviousPercentageVol = -1; 139 140 // Depth from root of current browsing. This can be used to move to root directly. 141 private int mBrowseDepth = 0; 142 143 // Browse tree. 144 private BrowseTree mBrowseTree = new BrowseTree(); 145 146 AvrcpControllerStateMachine(Context context) { 147 super(TAG); 148 mContext = context; 149 150 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 151 IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION); 152 mContext.registerReceiver(mBroadcastReceiver, filter); 153 154 mDisconnected = new Disconnected(); 155 mConnected = new Connected(); 156 157 // Used to change folder path and fetch the new folder listing. 158 mSetBrowsedPlayer = new SetBrowsedPlayer(); 159 mSetAddrPlayer = new SetAddresedPlayerAndPlayItem(); 160 mChangeFolderPath = new ChangeFolderPath(); 161 mGetFolderList = new GetFolderList(); 162 mGetPlayerListing = new GetPlayerListing(); 163 mMoveToRoot = new MoveToRoot(); 164 165 addState(mDisconnected); 166 addState(mConnected); 167 168 // Any action that needs blocking other requests to the state machine will be implemented as 169 // a separate substate of the mConnected state. Once transtition to the sub-state we should 170 // only handle the messages that are relevant to the sub-action. Everything else should be 171 // deferred so that once we transition to the mConnected we can process them hence. 172 addState(mSetBrowsedPlayer, mConnected); 173 addState(mSetAddrPlayer, mConnected); 174 addState(mChangeFolderPath, mConnected); 175 addState(mGetFolderList, mConnected); 176 addState(mGetPlayerListing, mConnected); 177 addState(mMoveToRoot, mConnected); 178 179 setInitialState(mDisconnected); 180 } 181 182 class Disconnected extends State { 183 184 @Override 185 public boolean processMessage(Message msg) { 186 if (DBG) Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what)); 187 switch (msg.what) { 188 case MESSAGE_PROCESS_CONNECTION_CHANGE: 189 if (msg.arg1 == BluetoothProfile.STATE_CONNECTED) { 190 mBrowseTree.init(); 191 transitionTo(mConnected); 192 BluetoothDevice rtDevice = (BluetoothDevice) msg.obj; 193 synchronized (mLock) { 194 mRemoteDevice = new RemoteDevice(rtDevice); 195 mAddressedPlayer = new AvrcpPlayer(); 196 mIsConnected = true; 197 } 198 MetricsLogger.logProfileConnectionEvent( 199 BluetoothMetricsProto.ProfileId.AVRCP_CONTROLLER); 200 Intent intent = new Intent( 201 BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED); 202 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 203 BluetoothProfile.STATE_DISCONNECTED); 204 intent.putExtra(BluetoothProfile.EXTRA_STATE, 205 BluetoothProfile.STATE_CONNECTED); 206 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice); 207 mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 208 } 209 break; 210 211 default: 212 Log.w(TAG, 213 "Currently Disconnected not handling " + dumpMessageString(msg.what)); 214 return false; 215 } 216 return true; 217 } 218 } 219 220 class Connected extends State { 221 @Override 222 public boolean processMessage(Message msg) { 223 if (DBG) Log.d(TAG, " HandleMessage: " + dumpMessageString(msg.what)); 224 A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService(); 225 synchronized (mLock) { 226 switch (msg.what) { 227 case MESSAGE_SEND_PASS_THROUGH_CMD: 228 BluetoothDevice device = (BluetoothDevice) msg.obj; 229 AvrcpControllerService.sendPassThroughCommandNative( 230 Utils.getByteAddress(device), msg.arg1, msg.arg2); 231 if (a2dpSinkService != null) { 232 if (DBG) Log.d(TAG, " inform AVRCP Commands to A2DP Sink "); 233 a2dpSinkService.informAvrcpPassThroughCmd(device, msg.arg1, msg.arg2); 234 } 235 break; 236 237 case MESSAGE_SEND_GROUP_NAVIGATION_CMD: 238 AvrcpControllerService.sendGroupNavigationCommandNative( 239 mRemoteDevice.getBluetoothAddress(), msg.arg1, msg.arg2); 240 break; 241 242 case MESSAGE_GET_NOW_PLAYING_LIST: 243 mGetFolderList.setFolder((String) msg.obj); 244 mGetFolderList.setBounds((int) msg.arg1, (int) msg.arg2); 245 mGetFolderList.setScope(AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING); 246 transitionTo(mGetFolderList); 247 break; 248 249 case MESSAGE_GET_FOLDER_LIST: 250 // Whenever we transition we set the information for folder we need to 251 // return result. 252 mGetFolderList.setBounds(msg.arg1, msg.arg2); 253 mGetFolderList.setFolder((String) msg.obj); 254 mGetFolderList.setScope(AvrcpControllerService.BROWSE_SCOPE_VFS); 255 transitionTo(mGetFolderList); 256 break; 257 258 case MESSAGE_GET_PLAYER_LIST: 259 AvrcpControllerService.getPlayerListNative( 260 mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1, 261 (byte) msg.arg2); 262 transitionTo(mGetPlayerListing); 263 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 264 break; 265 266 case MESSAGE_CHANGE_FOLDER_PATH: { 267 int direction = msg.arg1; 268 Bundle b = (Bundle) msg.obj; 269 String uid = b.getString(AvrcpControllerService.EXTRA_FOLDER_BT_ID); 270 String fid = b.getString(AvrcpControllerService.EXTRA_FOLDER_ID); 271 272 // String is encoded as a Hex String (mostly for display purposes) 273 // hence convert this back to real byte string. 274 AvrcpControllerService.changeFolderPathNative( 275 mRemoteDevice.getBluetoothAddress(), (byte) msg.arg1, 276 AvrcpControllerService.hexStringToByteUID(uid)); 277 mChangeFolderPath.setFolder(fid); 278 transitionTo(mChangeFolderPath); 279 sendMessage(MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT, (byte) msg.arg1); 280 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 281 break; 282 } 283 284 case MESSAGE_FETCH_ATTR_AND_PLAY_ITEM: { 285 int scope = msg.arg1; 286 String playItemUid = (String) msg.obj; 287 BrowseTree.BrowseNode currBrPlayer = mBrowseTree.getCurrentBrowsedPlayer(); 288 BrowseTree.BrowseNode currAddrPlayer = 289 mBrowseTree.getCurrentAddressedPlayer(); 290 if (DBG) { 291 Log.d(TAG, "currBrPlayer " + currBrPlayer + " currAddrPlayer " 292 + currAddrPlayer); 293 } 294 295 if (currBrPlayer == null || currBrPlayer.equals(currAddrPlayer)) { 296 // String is encoded as a Hex String (mostly for display purposes) 297 // hence convert this back to real byte string. 298 // NOTE: It may be possible that sending play while the same item is 299 // playing leads to reset of track. 300 AvrcpControllerService.playItemNative( 301 mRemoteDevice.getBluetoothAddress(), (byte) scope, 302 AvrcpControllerService.hexStringToByteUID(playItemUid), 303 (int) 0); 304 } else { 305 // Send out the request for setting addressed player. 306 AvrcpControllerService.setAddressedPlayerNative( 307 mRemoteDevice.getBluetoothAddress(), 308 currBrPlayer.getPlayerID()); 309 mSetAddrPlayer.setItemAndScope(currBrPlayer.getID(), playItemUid, 310 scope); 311 transitionTo(mSetAddrPlayer); 312 } 313 break; 314 } 315 316 case MESSAGE_SET_BROWSED_PLAYER: { 317 AvrcpControllerService.setBrowsedPlayerNative( 318 mRemoteDevice.getBluetoothAddress(), (int) msg.arg1); 319 mSetBrowsedPlayer.setFolder((String) msg.obj); 320 transitionTo(mSetBrowsedPlayer); 321 break; 322 } 323 324 case MESSAGE_PROCESS_SET_ADDRESSED_PLAYER: 325 AvrcpControllerService.getPlayerListNative( 326 mRemoteDevice.getBluetoothAddress(), 0, 255); 327 transitionTo(mGetPlayerListing); 328 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 329 break; 330 331 332 case MESSAGE_PROCESS_CONNECTION_CHANGE: 333 if (msg.arg1 == BluetoothProfile.STATE_DISCONNECTED) { 334 synchronized (mLock) { 335 mIsConnected = false; 336 mRemoteDevice = null; 337 } 338 mBrowseTree.clear(); 339 transitionTo(mDisconnected); 340 BluetoothDevice rtDevice = (BluetoothDevice) msg.obj; 341 Intent intent = new Intent( 342 BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED); 343 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 344 BluetoothProfile.STATE_CONNECTED); 345 intent.putExtra(BluetoothProfile.EXTRA_STATE, 346 BluetoothProfile.STATE_DISCONNECTED); 347 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, rtDevice); 348 mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 349 } 350 break; 351 352 case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE: 353 // Service tells us if the browse is connected or disconnected. 354 // This is useful only for deciding whether to send browse commands rest of 355 // the connection state handling should be done via the message 356 // MESSAGE_PROCESS_CONNECTION_CHANGE. 357 Intent intent = new Intent( 358 AvrcpControllerService.ACTION_BROWSE_CONNECTION_STATE_CHANGED); 359 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, (BluetoothDevice) msg.obj); 360 if (DBG) { 361 Log.d(TAG, "Browse connection state " + msg.arg1); 362 } 363 if (msg.arg1 == 1) { 364 intent.putExtra(BluetoothProfile.EXTRA_STATE, 365 BluetoothProfile.STATE_CONNECTED); 366 } else if (msg.arg1 == 0) { 367 intent.putExtra(BluetoothProfile.EXTRA_STATE, 368 BluetoothProfile.STATE_DISCONNECTED); 369 // If browse is disconnected, the next time we connect we should 370 // be at the ROOT. 371 mBrowseDepth = 0; 372 } else { 373 Log.w(TAG, "Incorrect browse state " + msg.arg1); 374 } 375 376 mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 377 break; 378 379 case MESSAGE_PROCESS_RC_FEATURES: 380 mRemoteDevice.setRemoteFeatures(msg.arg1); 381 break; 382 383 case MESSAGE_PROCESS_SET_ABS_VOL_CMD: 384 mVolumeChangedNotificationsToIgnore++; 385 removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT); 386 sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT, 387 ABS_VOL_TIMEOUT_MILLIS); 388 setAbsVolume(msg.arg1, msg.arg2); 389 break; 390 391 case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: { 392 mRemoteDevice.setNotificationLabel(msg.arg1); 393 mRemoteDevice.setAbsVolNotificationRequested(true); 394 int percentageVol = getVolumePercentage(); 395 if (DBG) { 396 Log.d(TAG, " Sending Interim Response = " + percentageVol + " label " 397 + msg.arg1); 398 } 399 AvrcpControllerService.sendRegisterAbsVolRspNative( 400 mRemoteDevice.getBluetoothAddress(), NOTIFICATION_RSP_TYPE_INTERIM, 401 percentageVol, mRemoteDevice.getNotificationLabel()); 402 } 403 break; 404 405 case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: { 406 if (mVolumeChangedNotificationsToIgnore > 0) { 407 mVolumeChangedNotificationsToIgnore--; 408 if (mVolumeChangedNotificationsToIgnore == 0) { 409 removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT); 410 } 411 } else { 412 if (mRemoteDevice.getAbsVolNotificationRequested()) { 413 int percentageVol = getVolumePercentage(); 414 if (percentageVol != mPreviousPercentageVol) { 415 AvrcpControllerService.sendRegisterAbsVolRspNative( 416 mRemoteDevice.getBluetoothAddress(), 417 NOTIFICATION_RSP_TYPE_CHANGED, percentageVol, 418 mRemoteDevice.getNotificationLabel()); 419 mPreviousPercentageVol = percentageVol; 420 mRemoteDevice.setAbsVolNotificationRequested(false); 421 } 422 } 423 } 424 } 425 break; 426 427 case MESSAGE_INTERNAL_ABS_VOL_TIMEOUT: 428 // Volume changed notifications should come back promptly from the 429 // AudioManager, if for some reason some notifications were squashed don't 430 // prevent future notifications. 431 if (DBG) Log.d(TAG, "Timed out on volume changed notification"); 432 mVolumeChangedNotificationsToIgnore = 0; 433 break; 434 435 case MESSAGE_PROCESS_TRACK_CHANGED: 436 // Music start playing automatically and update Metadata 437 mAddressedPlayer.updateCurrentTrack((TrackInfo) msg.obj); 438 broadcastMetaDataChanged( 439 mAddressedPlayer.getCurrentTrack().getMediaMetaData()); 440 break; 441 442 case MESSAGE_PROCESS_PLAY_POS_CHANGED: 443 if (msg.arg2 != -1) { 444 mAddressedPlayer.setPlayTime(msg.arg2); 445 broadcastPlayBackStateChanged(getCurrentPlayBackState()); 446 } 447 break; 448 449 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: 450 int status = msg.arg1; 451 mAddressedPlayer.setPlayStatus(status); 452 broadcastPlayBackStateChanged(getCurrentPlayBackState()); 453 if (status == PlaybackState.STATE_PLAYING) { 454 a2dpSinkService.informTGStatePlaying(mRemoteDevice.mBTDevice, true); 455 } else if (status == PlaybackState.STATE_PAUSED 456 || status == PlaybackState.STATE_STOPPED) { 457 a2dpSinkService.informTGStatePlaying(mRemoteDevice.mBTDevice, false); 458 } 459 break; 460 461 default: 462 return false; 463 } 464 } 465 return true; 466 } 467 } 468 469 // Handle the change folder path meta-action. 470 // a) Send Change folder command 471 // b) Once successful transition to folder fetch state. 472 class ChangeFolderPath extends CmdState { 473 private static final String STATE_TAG = "AVRCPSM.ChangeFolderPath"; 474 private int mTmpIncrDirection; 475 private String mID = ""; 476 477 public void setFolder(String id) { 478 mID = id; 479 } 480 481 @Override 482 public void enter() { 483 super.enter(); 484 mTmpIncrDirection = -1; 485 } 486 487 @Override 488 public boolean processMessage(Message msg) { 489 if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what); 490 switch (msg.what) { 491 case MESSAGE_INTERNAL_BROWSE_DEPTH_INCREMENT: 492 mTmpIncrDirection = msg.arg1; 493 break; 494 495 case MESSAGE_PROCESS_FOLDER_PATH: { 496 // Fetch the listing of objects in this folder. 497 if (DBG) { 498 Log.d(STATE_TAG, 499 "MESSAGE_PROCESS_FOLDER_PATH returned " + msg.arg1 + " elements"); 500 } 501 502 // Update the folder depth. 503 if (mTmpIncrDirection 504 == AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP) { 505 mBrowseDepth -= 1; 506 } else if (mTmpIncrDirection 507 == AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN) { 508 mBrowseDepth += 1; 509 } else { 510 throw new IllegalStateException("incorrect nav " + mTmpIncrDirection); 511 } 512 if (DBG) Log.d(STATE_TAG, "New browse depth " + mBrowseDepth); 513 514 if (msg.arg1 > 0) { 515 sendMessage(MESSAGE_GET_FOLDER_LIST, 0, msg.arg1 - 1, mID); 516 } else { 517 // Return an empty response to the upper layer. 518 broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST); 519 } 520 mBrowseTree.setCurrentBrowsedFolder(mID); 521 transitionTo(mConnected); 522 break; 523 } 524 525 case MESSAGE_INTERNAL_CMD_TIMEOUT: 526 // We timed out changing folders. It is imperative we tell 527 // the upper layers that we failed by giving them an empty list. 528 Log.e(STATE_TAG, "change folder failed, sending empty list."); 529 broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST); 530 transitionTo(mConnected); 531 break; 532 533 case MESSAGE_SEND_PASS_THROUGH_CMD: 534 case MESSAGE_SEND_GROUP_NAVIGATION_CMD: 535 case MESSAGE_PROCESS_SET_ABS_VOL_CMD: 536 case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: 537 case MESSAGE_PROCESS_TRACK_CHANGED: 538 case MESSAGE_PROCESS_PLAY_POS_CHANGED: 539 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: 540 case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: 541 case MESSAGE_STOP_METADATA_BROADCASTS: 542 case MESSAGE_START_METADATA_BROADCASTS: 543 case MESSAGE_PROCESS_CONNECTION_CHANGE: 544 case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE: 545 // All of these messages should be handled by parent state immediately. 546 return false; 547 548 default: 549 if (DBG) { 550 Log.d(STATE_TAG, "deferring message " + msg.what + " to Connected state."); 551 } 552 deferMessage(msg); 553 } 554 return true; 555 } 556 } 557 558 // Handle the get folder listing action 559 // a) Fetch the listing of folders 560 // b) Once completed return the object listing 561 class GetFolderList extends CmdState { 562 private static final String STATE_TAG = "AVRCPSM.GetFolderList"; 563 564 String mID = ""; 565 int mStartInd; 566 int mEndInd; 567 int mCurrInd; 568 int mScope; 569 private ArrayList<MediaItem> mFolderList = new ArrayList<>(); 570 571 @Override 572 public void enter() { 573 // Setup the timeouts. 574 super.enter(); 575 mCurrInd = 0; 576 mFolderList.clear(); 577 callNativeFunctionForScope(mStartInd, 578 Math.min(mEndInd, mStartInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1)); 579 } 580 581 public void setScope(int scope) { 582 mScope = scope; 583 } 584 585 public void setFolder(String id) { 586 if (DBG) Log.d(STATE_TAG, "Setting folder to " + id); 587 mID = id; 588 } 589 590 public void setBounds(int startInd, int endInd) { 591 if (DBG) { 592 Log.d(STATE_TAG, "startInd " + startInd + " endInd " + endInd); 593 } 594 mStartInd = startInd; 595 mEndInd = Math.min(endInd, MAX_FOLDER_ITEMS); 596 } 597 598 @Override 599 public boolean processMessage(Message msg) { 600 Log.d(STATE_TAG, "processMessage " + msg.what); 601 switch (msg.what) { 602 case MESSAGE_PROCESS_GET_FOLDER_ITEMS: 603 ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj; 604 mFolderList.addAll(folderList); 605 if (DBG) { 606 Log.d(STATE_TAG, 607 "Start " + mStartInd + " End " + mEndInd + " Curr " + mCurrInd 608 + " received " + folderList.size()); 609 } 610 mCurrInd += folderList.size(); 611 612 // Always update the node so that the user does not wait forever 613 // for the list to populate. 614 sendFolderBroadcastAndUpdateNode(); 615 616 if (mCurrInd > mEndInd || folderList.size() == 0) { 617 // If we have fetched all the elements or if the remotes sends us 0 elements 618 // (which can lead us into a loop since mCurrInd does not proceed) we simply 619 // abort. 620 transitionTo(mConnected); 621 } else { 622 // Fetch the next set of items. 623 callNativeFunctionForScope(mCurrInd, Math.min(mEndInd, 624 mCurrInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1)); 625 // Reset the timeout message since we are doing a new fetch now. 626 removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); 627 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 628 } 629 break; 630 631 case MESSAGE_INTERNAL_CMD_TIMEOUT: 632 // We have timed out to execute the request, we should simply send 633 // whatever listing we have gotten until now. 634 sendFolderBroadcastAndUpdateNode(); 635 transitionTo(mConnected); 636 break; 637 638 case MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE: 639 // If we have gotten an error for OUT OF RANGE we have 640 // already sent all the items to the client hence simply 641 // transition to Connected state here. 642 transitionTo(mConnected); 643 break; 644 645 case MESSAGE_CHANGE_FOLDER_PATH: 646 case MESSAGE_FETCH_ATTR_AND_PLAY_ITEM: 647 case MESSAGE_GET_PLAYER_LIST: 648 case MESSAGE_GET_NOW_PLAYING_LIST: 649 case MESSAGE_SET_BROWSED_PLAYER: 650 // A new request has come in, no need to fetch more. 651 mEndInd = 0; 652 deferMessage(msg); 653 break; 654 655 case MESSAGE_SEND_PASS_THROUGH_CMD: 656 case MESSAGE_SEND_GROUP_NAVIGATION_CMD: 657 case MESSAGE_PROCESS_SET_ABS_VOL_CMD: 658 case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: 659 case MESSAGE_PROCESS_TRACK_CHANGED: 660 case MESSAGE_PROCESS_PLAY_POS_CHANGED: 661 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: 662 case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: 663 case MESSAGE_STOP_METADATA_BROADCASTS: 664 case MESSAGE_START_METADATA_BROADCASTS: 665 case MESSAGE_PROCESS_CONNECTION_CHANGE: 666 case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE: 667 // All of these messages should be handled by parent state immediately. 668 return false; 669 670 default: 671 if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!"); 672 deferMessage(msg); 673 } 674 return true; 675 } 676 677 private void sendFolderBroadcastAndUpdateNode() { 678 BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(mID); 679 if (bn == null) { 680 Log.e(TAG, "Can not find BrowseNode by ID: " + mID); 681 return; 682 } 683 if (bn.isPlayer()) { 684 // Add the now playing folder. 685 MediaDescription.Builder mdb = new MediaDescription.Builder(); 686 mdb.setMediaId(BrowseTree.NOW_PLAYING_PREFIX + ":" + bn.getPlayerID()); 687 mdb.setTitle(BrowseTree.NOW_PLAYING_PREFIX); 688 Bundle mdBundle = new Bundle(); 689 mdBundle.putString(AvrcpControllerService.MEDIA_ITEM_UID_KEY, 690 BrowseTree.NOW_PLAYING_PREFIX + ":" + bn.getID()); 691 mdb.setExtras(mdBundle); 692 mFolderList.add(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE)); 693 } 694 mBrowseTree.refreshChildren(bn, mFolderList); 695 broadcastFolderList(mID, mFolderList); 696 697 // For now playing we need to set the current browsed folder here. 698 // For normal folders it is set after ChangeFolderPath. 699 if (mScope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) { 700 mBrowseTree.setCurrentBrowsedFolder(mID); 701 } 702 } 703 704 private void callNativeFunctionForScope(int start, int end) { 705 switch (mScope) { 706 case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING: 707 AvrcpControllerService.getNowPlayingListNative( 708 mRemoteDevice.getBluetoothAddress(), start, end); 709 break; 710 case AvrcpControllerService.BROWSE_SCOPE_VFS: 711 AvrcpControllerService.getFolderListNative(mRemoteDevice.getBluetoothAddress(), 712 start, end); 713 break; 714 default: 715 Log.e(STATE_TAG, "Scope " + mScope + " cannot be handled here."); 716 } 717 } 718 } 719 720 // Handle the get player listing action 721 // a) Fetch the listing of players 722 // b) Once completed return the object listing 723 class GetPlayerListing extends CmdState { 724 private static final String STATE_TAG = "AVRCPSM.GetPlayerList"; 725 726 @Override 727 public boolean processMessage(Message msg) { 728 if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what); 729 switch (msg.what) { 730 case MESSAGE_PROCESS_GET_PLAYER_ITEMS: 731 List<AvrcpPlayer> playerList = (List<AvrcpPlayer>) msg.obj; 732 mBrowseTree.refreshChildren(BrowseTree.ROOT, playerList); 733 ArrayList<MediaItem> mediaItemList = new ArrayList<>(); 734 for (BrowseTree.BrowseNode c : mBrowseTree.findBrowseNodeByID(BrowseTree.ROOT) 735 .getChildren()) { 736 mediaItemList.add(c.getMediaItem()); 737 } 738 broadcastFolderList(BrowseTree.ROOT, mediaItemList); 739 mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT); 740 transitionTo(mConnected); 741 break; 742 743 case MESSAGE_INTERNAL_CMD_TIMEOUT: 744 // We have timed out to execute the request. 745 // Send an empty list here. 746 broadcastFolderList(BrowseTree.ROOT, EMPTY_MEDIA_ITEM_LIST); 747 transitionTo(mConnected); 748 break; 749 750 case MESSAGE_SEND_PASS_THROUGH_CMD: 751 case MESSAGE_SEND_GROUP_NAVIGATION_CMD: 752 case MESSAGE_PROCESS_SET_ABS_VOL_CMD: 753 case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: 754 case MESSAGE_PROCESS_TRACK_CHANGED: 755 case MESSAGE_PROCESS_PLAY_POS_CHANGED: 756 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: 757 case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: 758 case MESSAGE_STOP_METADATA_BROADCASTS: 759 case MESSAGE_START_METADATA_BROADCASTS: 760 case MESSAGE_PROCESS_CONNECTION_CHANGE: 761 case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE: 762 // All of these messages should be handled by parent state immediately. 763 return false; 764 765 default: 766 if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!"); 767 deferMessage(msg); 768 } 769 return true; 770 } 771 } 772 773 class MoveToRoot extends CmdState { 774 private static final String STATE_TAG = "AVRCPSM.MoveToRoot"; 775 private String mID = ""; 776 777 public void setFolder(String id) { 778 if (DBG) Log.d(STATE_TAG, "setFolder " + id); 779 mID = id; 780 } 781 782 @Override 783 public void enter() { 784 // Setup the timeouts. 785 super.enter(); 786 787 // We need to move mBrowseDepth levels up. The following message is 788 // completely internal to this state. 789 sendMessage(MESSAGE_INTERNAL_MOVE_N_LEVELS_UP); 790 } 791 792 @Override 793 public boolean processMessage(Message msg) { 794 if (DBG) { 795 Log.d(STATE_TAG, "processMessage " + msg.what + " browse depth " + mBrowseDepth); 796 } 797 switch (msg.what) { 798 case MESSAGE_INTERNAL_MOVE_N_LEVELS_UP: 799 if (mBrowseDepth == 0) { 800 Log.w(STATE_TAG, "Already in root!"); 801 transitionTo(mConnected); 802 sendMessage(MESSAGE_GET_FOLDER_LIST, 0, 0xff, mID); 803 } else { 804 AvrcpControllerService.changeFolderPathNative( 805 mRemoteDevice.getBluetoothAddress(), 806 (byte) AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP, 807 AvrcpControllerService.hexStringToByteUID(null)); 808 } 809 break; 810 811 case MESSAGE_PROCESS_FOLDER_PATH: 812 mBrowseDepth -= 1; 813 if (DBG) Log.d(STATE_TAG, "New browse depth " + mBrowseDepth); 814 if (mBrowseDepth < 0) { 815 throw new IllegalArgumentException("Browse depth negative!"); 816 } 817 818 sendMessage(MESSAGE_INTERNAL_MOVE_N_LEVELS_UP); 819 break; 820 821 case MESSAGE_INTERNAL_CMD_TIMEOUT: 822 broadcastFolderList(BrowseTree.ROOT, EMPTY_MEDIA_ITEM_LIST); 823 transitionTo(mConnected); 824 break; 825 826 case MESSAGE_SEND_PASS_THROUGH_CMD: 827 case MESSAGE_SEND_GROUP_NAVIGATION_CMD: 828 case MESSAGE_PROCESS_SET_ABS_VOL_CMD: 829 case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: 830 case MESSAGE_PROCESS_TRACK_CHANGED: 831 case MESSAGE_PROCESS_PLAY_POS_CHANGED: 832 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: 833 case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: 834 case MESSAGE_STOP_METADATA_BROADCASTS: 835 case MESSAGE_START_METADATA_BROADCASTS: 836 case MESSAGE_PROCESS_CONNECTION_CHANGE: 837 case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE: 838 // All of these messages should be handled by parent state immediately. 839 return false; 840 841 default: 842 if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!"); 843 deferMessage(msg); 844 } 845 return true; 846 } 847 } 848 849 class SetBrowsedPlayer extends CmdState { 850 private static final String STATE_TAG = "AVRCPSM.SetBrowsedPlayer"; 851 String mID = ""; 852 853 public void setFolder(String id) { 854 mID = id; 855 } 856 857 @Override 858 public boolean processMessage(Message msg) { 859 if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what); 860 switch (msg.what) { 861 case MESSAGE_PROCESS_SET_BROWSED_PLAYER: 862 // Set the new depth. 863 if (DBG) Log.d(STATE_TAG, "player depth " + msg.arg2); 864 mBrowseDepth = msg.arg2; 865 866 // If we already on top of player and there is no content. 867 // This should very rarely happen. 868 if (mBrowseDepth == 0 && msg.arg1 == 0) { 869 broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST); 870 transitionTo(mConnected); 871 } else { 872 // Otherwise move to root and fetch the listing. 873 // the MoveToRoot#enter() function takes care of fetch. 874 mMoveToRoot.setFolder(mID); 875 transitionTo(mMoveToRoot); 876 } 877 mBrowseTree.setCurrentBrowsedFolder(mID); 878 // Also set the browsed player here. 879 mBrowseTree.setCurrentBrowsedPlayer(mID); 880 break; 881 882 case MESSAGE_INTERNAL_CMD_TIMEOUT: 883 broadcastFolderList(mID, EMPTY_MEDIA_ITEM_LIST); 884 transitionTo(mConnected); 885 break; 886 887 case MESSAGE_SEND_PASS_THROUGH_CMD: 888 case MESSAGE_SEND_GROUP_NAVIGATION_CMD: 889 case MESSAGE_PROCESS_SET_ABS_VOL_CMD: 890 case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: 891 case MESSAGE_PROCESS_TRACK_CHANGED: 892 case MESSAGE_PROCESS_PLAY_POS_CHANGED: 893 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: 894 case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: 895 case MESSAGE_STOP_METADATA_BROADCASTS: 896 case MESSAGE_START_METADATA_BROADCASTS: 897 case MESSAGE_PROCESS_CONNECTION_CHANGE: 898 case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE: 899 // All of these messages should be handled by parent state immediately. 900 return false; 901 902 default: 903 if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!"); 904 deferMessage(msg); 905 } 906 return true; 907 } 908 } 909 910 class SetAddresedPlayerAndPlayItem extends CmdState { 911 private static final String STATE_TAG = "AVRCPSM.SetAddresedPlayerAndPlayItem"; 912 int mScope; 913 String mPlayItemId; 914 String mAddrPlayerId; 915 916 public void setItemAndScope(String addrPlayerId, String playItemId, int scope) { 917 mAddrPlayerId = addrPlayerId; 918 mPlayItemId = playItemId; 919 mScope = scope; 920 } 921 922 @Override 923 public boolean processMessage(Message msg) { 924 if (DBG) Log.d(STATE_TAG, "processMessage " + msg.what); 925 switch (msg.what) { 926 case MESSAGE_PROCESS_SET_ADDRESSED_PLAYER: 927 // Set the new addressed player. 928 mBrowseTree.setCurrentAddressedPlayer(mAddrPlayerId); 929 930 // And now play the item. 931 AvrcpControllerService.playItemNative(mRemoteDevice.getBluetoothAddress(), 932 (byte) mScope, AvrcpControllerService.hexStringToByteUID(mPlayItemId), 933 (int) 0); 934 935 // Transition to connected state here. 936 transitionTo(mConnected); 937 break; 938 939 case MESSAGE_INTERNAL_CMD_TIMEOUT: 940 transitionTo(mConnected); 941 break; 942 943 case MESSAGE_SEND_PASS_THROUGH_CMD: 944 case MESSAGE_SEND_GROUP_NAVIGATION_CMD: 945 case MESSAGE_PROCESS_SET_ABS_VOL_CMD: 946 case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: 947 case MESSAGE_PROCESS_TRACK_CHANGED: 948 case MESSAGE_PROCESS_PLAY_POS_CHANGED: 949 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: 950 case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION: 951 case MESSAGE_STOP_METADATA_BROADCASTS: 952 case MESSAGE_START_METADATA_BROADCASTS: 953 case MESSAGE_PROCESS_CONNECTION_CHANGE: 954 case MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE: 955 // All of these messages should be handled by parent state immediately. 956 return false; 957 958 default: 959 if (DBG) Log.d(STATE_TAG, "deferring message " + msg.what + " to connected!"); 960 deferMessage(msg); 961 } 962 return true; 963 } 964 } 965 966 // Class template for commands. Each state should do the following: 967 // (a) In enter() send a timeout message which could be tracked in the 968 // processMessage() stage. 969 // (b) In exit() remove all the timeouts. 970 // 971 // Essentially the lifecycle of a timeout should be bounded to a CmdState always. 972 abstract class CmdState extends State { 973 @Override 974 public void enter() { 975 sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS); 976 } 977 978 @Override 979 public void exit() { 980 removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT); 981 } 982 } 983 984 // Interface APIs 985 boolean isConnected() { 986 synchronized (mLock) { 987 return mIsConnected; 988 } 989 } 990 991 void doQuit() { 992 try { 993 mContext.unregisterReceiver(mBroadcastReceiver); 994 } catch (IllegalArgumentException expected) { 995 // If the receiver was never registered unregister will throw an 996 // IllegalArgumentException. 997 } 998 quit(); 999 } 1000 1001 void dump(StringBuilder sb) { 1002 ProfileService.println(sb, "StateMachine: " + this.toString()); 1003 } 1004 1005 MediaMetadata getCurrentMetaData() { 1006 synchronized (mLock) { 1007 if (mAddressedPlayer != null && mAddressedPlayer.getCurrentTrack() != null) { 1008 MediaMetadata mmd = mAddressedPlayer.getCurrentTrack().getMediaMetaData(); 1009 if (DBG) { 1010 Log.d(TAG, "getCurrentMetaData mmd " + mmd); 1011 } 1012 } 1013 return EMPTY_MEDIA_METADATA; 1014 } 1015 } 1016 1017 PlaybackState getCurrentPlayBackState() { 1018 return getCurrentPlayBackState(true); 1019 } 1020 1021 PlaybackState getCurrentPlayBackState(boolean cached) { 1022 if (cached) { 1023 synchronized (mLock) { 1024 if (mAddressedPlayer == null) { 1025 return new PlaybackState.Builder().setState(PlaybackState.STATE_ERROR, 1026 PlaybackState.PLAYBACK_POSITION_UNKNOWN, 0).build(); 1027 } 1028 return mAddressedPlayer.getPlaybackState(); 1029 } 1030 } else { 1031 // Issue a native request, we return NULL since this is only for PTS. 1032 AvrcpControllerService.getPlaybackStateNative(mRemoteDevice.getBluetoothAddress()); 1033 return null; 1034 } 1035 } 1036 1037 // Entry point to the state machine where the services should call to fetch children 1038 // for a specific node. It checks if the currently browsed node is the same as the one being 1039 // asked for, in that case it returns the currently cached children. This saves bandwidth and 1040 // also if we are already fetching elements for a current folder (since we need to batch 1041 // fetches) then we should not submit another request but simply return what we have fetched 1042 // until now. 1043 // 1044 // It handles fetches to all VFS, Now Playing and Media Player lists. 1045 void getChildren(String parentMediaId, int start, int items) { 1046 BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(parentMediaId); 1047 if (bn == null) { 1048 Log.e(TAG, "Invalid folder to browse " + mBrowseTree); 1049 broadcastFolderList(parentMediaId, EMPTY_MEDIA_ITEM_LIST); 1050 return; 1051 } 1052 1053 if (DBG) { 1054 Log.d(TAG, "To Browse folder " + bn + " is cached " + bn.isCached() + " current folder " 1055 + mBrowseTree.getCurrentBrowsedFolder()); 1056 } 1057 if (bn.equals(mBrowseTree.getCurrentBrowsedFolder()) && bn.isCached()) { 1058 if (DBG) { 1059 Log.d(TAG, "Same cached folder -- returning existing children."); 1060 } 1061 BrowseTree.BrowseNode n = mBrowseTree.findBrowseNodeByID(parentMediaId); 1062 ArrayList<MediaItem> childrenList = new ArrayList<MediaItem>(); 1063 for (BrowseTree.BrowseNode cn : n.getChildren()) { 1064 childrenList.add(cn.getMediaItem()); 1065 } 1066 broadcastFolderList(parentMediaId, childrenList); 1067 return; 1068 } 1069 1070 Message msg = null; 1071 int btDirection = mBrowseTree.getDirection(parentMediaId); 1072 BrowseTree.BrowseNode currFol = mBrowseTree.getCurrentBrowsedFolder(); 1073 if (DBG) { 1074 Log.d(TAG, "Browse direction parent " + mBrowseTree.getCurrentBrowsedFolder() + " req " 1075 + parentMediaId + " direction " + btDirection); 1076 } 1077 if (BrowseTree.ROOT.equals(parentMediaId)) { 1078 // Root contains the list of players. 1079 msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items); 1080 } else if (bn.isPlayer() && btDirection != BrowseTree.DIRECTION_SAME) { 1081 // Set browsed (and addressed player) as the new player. 1082 // This should fetch the list of folders. 1083 msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, 1084 bn.getPlayerID(), 0, bn.getID()); 1085 } else if (bn.isNowPlaying()) { 1086 // Issue a request to fetch the items. 1087 msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST, start, 1088 items, parentMediaId); 1089 } else { 1090 // Only change folder if desired. If an app refreshes a folder 1091 // (because it resumed etc) and current folder does not change 1092 // then we can simply fetch list. 1093 1094 // We exempt two conditions from change folder: 1095 // a) If the new folder is the same as current folder (refresh of UI) 1096 // b) If the new folder is ROOT and current folder is NOW_PLAYING (or vice-versa) 1097 // In this condition we 'fake' child-parent hierarchy but it does not exist in 1098 // bluetooth world. 1099 boolean isNowPlayingToRoot = 1100 currFol.isNowPlaying() && bn.getID().equals(BrowseTree.ROOT); 1101 if (!isNowPlayingToRoot) { 1102 // Find the direction of traversal. 1103 int direction = -1; 1104 if (DBG) Log.d(TAG, "Browse direction " + currFol + " " + bn + " = " + btDirection); 1105 if (btDirection == BrowseTree.DIRECTION_UNKNOWN) { 1106 Log.w(TAG, "parent " + bn + " is not a direct " 1107 + "successor or predeccessor of current folder " + currFol); 1108 broadcastFolderList(parentMediaId, EMPTY_MEDIA_ITEM_LIST); 1109 return; 1110 } 1111 1112 if (btDirection == BrowseTree.DIRECTION_DOWN) { 1113 direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN; 1114 } else if (btDirection == BrowseTree.DIRECTION_UP) { 1115 direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP; 1116 } 1117 1118 Bundle b = new Bundle(); 1119 b.putString(AvrcpControllerService.EXTRA_FOLDER_ID, bn.getID()); 1120 b.putString(AvrcpControllerService.EXTRA_FOLDER_BT_ID, bn.getFolderUID()); 1121 msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH, 1122 direction, 0, b); 1123 } else { 1124 // Fetch the listing without changing paths. 1125 msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start, 1126 items, bn.getFolderUID()); 1127 } 1128 } 1129 1130 if (msg != null) { 1131 sendMessage(msg); 1132 } 1133 } 1134 1135 public void fetchAttrAndPlayItem(String uid) { 1136 BrowseTree.BrowseNode currItem = mBrowseTree.findFolderByIDLocked(uid); 1137 BrowseTree.BrowseNode currFolder = mBrowseTree.getCurrentBrowsedFolder(); 1138 if (DBG) Log.d(TAG, "fetchAttrAndPlayItem mediaId=" + uid + " node=" + currItem); 1139 if (currItem != null) { 1140 int scope = currFolder.isNowPlaying() ? AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING 1141 : AvrcpControllerService.BROWSE_SCOPE_VFS; 1142 Message msg = 1143 obtainMessage(AvrcpControllerStateMachine.MESSAGE_FETCH_ATTR_AND_PLAY_ITEM, 1144 scope, 0, currItem.getFolderUID()); 1145 sendMessage(msg); 1146 } 1147 } 1148 1149 private void broadcastMetaDataChanged(MediaMetadata metadata) { 1150 Intent intent = new Intent(AvrcpControllerService.ACTION_TRACK_EVENT); 1151 intent.putExtra(AvrcpControllerService.EXTRA_METADATA, metadata); 1152 if (VDBG) { 1153 Log.d(TAG, " broadcastMetaDataChanged = " + metadata.getDescription()); 1154 } 1155 mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 1156 } 1157 1158 private void broadcastFolderList(String id, ArrayList<MediaItem> items) { 1159 Intent intent = new Intent(AvrcpControllerService.ACTION_FOLDER_LIST); 1160 if (VDBG) Log.d(TAG, "broadcastFolderList id " + id + " items " + items); 1161 intent.putExtra(AvrcpControllerService.EXTRA_FOLDER_ID, id); 1162 intent.putParcelableArrayListExtra(AvrcpControllerService.EXTRA_FOLDER_LIST, items); 1163 mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 1164 } 1165 1166 private void broadcastPlayBackStateChanged(PlaybackState state) { 1167 Intent intent = new Intent(AvrcpControllerService.ACTION_TRACK_EVENT); 1168 intent.putExtra(AvrcpControllerService.EXTRA_PLAYBACK, state); 1169 if (DBG) { 1170 Log.d(TAG, " broadcastPlayBackStateChanged = " + state.toString()); 1171 } 1172 mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM); 1173 } 1174 1175 private void setAbsVolume(int absVol, int label) { 1176 int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 1177 int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 1178 // Ignore first volume command since phone may not know difference between stream volume 1179 // and amplifier volume. 1180 if (mRemoteDevice.getFirstAbsVolCmdRecvd()) { 1181 int newIndex = (maxVolume * absVol) / ABS_VOL_BASE; 1182 if (DBG) { 1183 Log.d(TAG, " setAbsVolume =" + absVol + " maxVol = " + maxVolume 1184 + " cur = " + currIndex + " new = " + newIndex); 1185 } 1186 /* 1187 * In some cases change in percentage is not sufficient enough to warrant 1188 * change in index values which are in range of 0-15. For such cases 1189 * no action is required 1190 */ 1191 if (newIndex != currIndex) { 1192 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex, 1193 AudioManager.FLAG_SHOW_UI); 1194 } 1195 } else { 1196 mRemoteDevice.setFirstAbsVolCmdRecvd(); 1197 absVol = (currIndex * ABS_VOL_BASE) / maxVolume; 1198 if (DBG) Log.d(TAG, " SetAbsVol recvd for first time, respond with " + absVol); 1199 } 1200 AvrcpControllerService.sendAbsVolRspNative(mRemoteDevice.getBluetoothAddress(), absVol, 1201 label); 1202 } 1203 1204 private int getVolumePercentage() { 1205 int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 1206 int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 1207 int percentageVol = ((currIndex * ABS_VOL_BASE) / maxVolume); 1208 return percentageVol; 1209 } 1210 1211 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 1212 @Override 1213 public void onReceive(Context context, Intent intent) { 1214 String action = intent.getAction(); 1215 if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) { 1216 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 1217 if (streamType == AudioManager.STREAM_MUSIC) { 1218 sendMessage(MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION); 1219 } 1220 } 1221 } 1222 }; 1223 1224 public static String dumpMessageString(int message) { 1225 String str = "UNKNOWN"; 1226 switch (message) { 1227 case MESSAGE_SEND_PASS_THROUGH_CMD: 1228 str = "REQ_PASS_THROUGH_CMD"; 1229 break; 1230 case MESSAGE_SEND_GROUP_NAVIGATION_CMD: 1231 str = "REQ_GRP_NAV_CMD"; 1232 break; 1233 case MESSAGE_PROCESS_SET_ABS_VOL_CMD: 1234 str = "CB_SET_ABS_VOL_CMD"; 1235 break; 1236 case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION: 1237 str = "CB_REGISTER_ABS_VOL"; 1238 break; 1239 case MESSAGE_PROCESS_TRACK_CHANGED: 1240 str = "CB_TRACK_CHANGED"; 1241 break; 1242 case MESSAGE_PROCESS_PLAY_POS_CHANGED: 1243 str = "CB_PLAY_POS_CHANGED"; 1244 break; 1245 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED: 1246 str = "CB_PLAY_STATUS_CHANGED"; 1247 break; 1248 case MESSAGE_PROCESS_RC_FEATURES: 1249 str = "CB_RC_FEATURES"; 1250 break; 1251 case MESSAGE_PROCESS_CONNECTION_CHANGE: 1252 str = "CB_CONN_CHANGED"; 1253 break; 1254 default: 1255 str = Integer.toString(message); 1256 break; 1257 } 1258 return str; 1259 } 1260 1261 public static String displayBluetoothAvrcpSettings(BluetoothAvrcpPlayerSettings mSett) { 1262 StringBuffer sb = new StringBuffer(); 1263 int supportedSetting = mSett.getSettings(); 1264 if (VDBG) { 1265 Log.d(TAG, " setting: " + supportedSetting); 1266 } 1267 if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) { 1268 sb.append(" EQ : "); 1269 sb.append(Integer.toString(mSett.getSettingValue( 1270 BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER))); 1271 } 1272 if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) { 1273 sb.append(" REPEAT : "); 1274 sb.append(Integer.toString(mSett.getSettingValue( 1275 BluetoothAvrcpPlayerSettings.SETTING_REPEAT))); 1276 } 1277 if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) { 1278 sb.append(" SHUFFLE : "); 1279 sb.append(Integer.toString(mSett.getSettingValue( 1280 BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE))); 1281 } 1282 if ((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) { 1283 sb.append(" SCAN : "); 1284 sb.append(Integer.toString(mSett.getSettingValue( 1285 BluetoothAvrcpPlayerSettings.SETTING_SCAN))); 1286 } 1287 return sb.toString(); 1288 } 1289 } 1290