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