Home | History | Annotate | Download | only in avrcpcontroller
      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