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