AvrcpControllerStateMachine.java revision e5df60ac54d1d5d868f6fb674bc493fec144305f
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 || folderList.size() == 0) {
586                        // If we have fetched all the elements or if the remotes sends us 0 elements
587                        // (which can lead us into a loop since mCurrInd does not proceed) we simply
588                        // abort.
589                        transitionTo(mConnected);
590                    } else {
591                        // Fetch the next set of items.
592                        callNativeFunctionForScope(
593                            (byte) mCurrInd,
594                            (byte) Math.min(
595                                mEndInd, mCurrInd + GET_FOLDER_ITEMS_PAGINATION_SIZE - 1));
596                        // Reset the timeout message since we are doing a new fetch now.
597                        removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
598                        sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
599                    }
600                    break;
601
602                case MESSAGE_INTERNAL_CMD_TIMEOUT:
603                    // We have timed out to execute the request, we should simply send
604                    // whatever listing we have gotten until now.
605                    sendFolderBroadcastAndUpdateNode();
606                    transitionTo(mConnected);
607                    break;
608
609                case MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE:
610                    // If we have gotten an error for OUT OF RANGE we have
611                    // already sent all the items to the client hence simply
612                    // transition to Connected state here.
613                    transitionTo(mConnected);
614                    break;
615
616                default:
617                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
618                    deferMessage(msg);
619            }
620            return true;
621        }
622
623        private void sendFolderBroadcastAndUpdateNode() {
624            BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(mID);
625            if (bn.isPlayer()) {
626                // Add the now playing folder.
627                MediaDescription.Builder mdb = new MediaDescription.Builder();
628                mdb.setMediaId(BrowseTree.NOW_PLAYING_PREFIX + ":" +
629                    bn.getPlayerID());
630                mdb.setTitle(BrowseTree.NOW_PLAYING_PREFIX);
631                Bundle mdBundle = new Bundle();
632                mdBundle.putString(
633                    AvrcpControllerService.MEDIA_ITEM_UID_KEY,
634                    BrowseTree.NOW_PLAYING_PREFIX + ":" + bn.getID());
635                mdb.setExtras(mdBundle);
636                mFolderList.add(new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE));
637            }
638            mBrowseTree.refreshChildren(bn, mFolderList);
639            broadcastFolderList(mID, mFolderList);
640
641            // For now playing we need to set the current browsed folder here.
642            // For normal folders it is set after ChangeFolderPath.
643            if (mScope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
644                mBrowseTree.setCurrentBrowsedFolder(mID);
645            }
646        }
647
648        private void callNativeFunctionForScope(int start, int end) {
649            switch (mScope) {
650                case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING:
651                    AvrcpControllerService.getNowPlayingListNative(
652                        mRemoteDevice.getBluetoothAddress(), (byte) start, (byte) end);
653                    break;
654                case AvrcpControllerService.BROWSE_SCOPE_VFS:
655                    AvrcpControllerService.getFolderListNative(
656                        mRemoteDevice.getBluetoothAddress(), (byte) start, (byte) end);
657                    break;
658                default:
659                    Log.e(STATE_TAG, "Scope " + mScope + " cannot be handled here.");
660            }
661        }
662    }
663
664    // Handle the get player listing action
665    // a) Fetch the listing of players
666    // b) Once completed return the object listing
667    class GetPlayerListing extends CmdState {
668        private String STATE_TAG = "AVRCPSM.GetPlayerList";
669
670        @Override
671        public boolean processMessage(Message msg) {
672            Log.d(STATE_TAG, "processMessage " + msg);
673            switch (msg.what) {
674                case MESSAGE_PROCESS_GET_PLAYER_ITEMS:
675                    List<AvrcpPlayer> playerList =
676                        (List<AvrcpPlayer>) msg.obj;
677                    mBrowseTree.refreshChildren(BrowseTree.ROOT, playerList);
678                    ArrayList<MediaItem> mediaItemList = new ArrayList<>();
679                    for (BrowseTree.BrowseNode c :
680                            mBrowseTree.findBrowseNodeByID(BrowseTree.ROOT).getChildren()) {
681                        mediaItemList.add(c.getMediaItem());
682                    }
683                    broadcastFolderList(BrowseTree.ROOT, mediaItemList);
684                    mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT);
685                    transitionTo(mConnected);
686                    break;
687
688                case MESSAGE_INTERNAL_CMD_TIMEOUT:
689                    // We have timed out to execute the request.
690                    // Send an empty list here.
691                    broadcastFolderList(BrowseTree.ROOT, mEmptyMediaItemList);
692                    transitionTo(mConnected);
693                    break;
694
695                default:
696                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
697                    deferMessage(msg);
698            }
699            return true;
700        }
701    }
702
703    class MoveToRoot extends CmdState {
704        private String STATE_TAG = "AVRCPSM.MoveToRoot";
705        private String mID = "";
706
707        public void setFolder(String id) {
708            Log.d(STATE_TAG, "setFolder " + id);
709            mID = id;
710        }
711
712        @Override
713        public void enter() {
714            // Setup the timeouts.
715            super.enter();
716
717            // We need to move mBrowseDepth levels up. The following message is
718            // completely internal to this state.
719            sendMessage(MESSAGE_INTERNAL_MOVE_N_LEVELS_UP);
720        }
721
722        @Override
723        public boolean processMessage(Message msg) {
724            Log.d(STATE_TAG, "processMessage " + msg + " browse depth " + mBrowseDepth);
725            switch (msg.what) {
726                case MESSAGE_INTERNAL_MOVE_N_LEVELS_UP:
727                    if (mBrowseDepth == 0) {
728                        Log.w(STATE_TAG, "Already in root!");
729                        transitionTo(mConnected);
730                        sendMessage(MESSAGE_GET_FOLDER_LIST, 0, 0xff, mID);
731                    } else {
732                        AvrcpControllerService.changeFolderPathNative(
733                            mRemoteDevice.getBluetoothAddress(),
734                            (byte) AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP,
735                            AvrcpControllerService.hexStringToByteUID(null));
736                    }
737                    break;
738
739                case MESSAGE_PROCESS_FOLDER_PATH:
740                    mBrowseDepth -= 1;
741                    Log.d(STATE_TAG, "New browse depth " + mBrowseDepth);
742                    if (mBrowseDepth < 0) {
743                        throw new IllegalArgumentException("Browse depth negative!");
744                    }
745
746                    sendMessage(MESSAGE_INTERNAL_MOVE_N_LEVELS_UP);
747                    break;
748
749                default:
750                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
751                    deferMessage(msg);
752            }
753            return true;
754        }
755    }
756
757    class SetBrowsedPlayer extends CmdState {
758        private String STATE_TAG = "AVRCPSM.SetBrowsedPlayer";
759        String mID = "";
760
761        public void setFolder(String id) {
762            mID = id;
763        }
764
765        @Override
766        public boolean processMessage(Message msg) {
767            Log.d(STATE_TAG, "processMessage " + msg);
768            switch (msg.what) {
769                case MESSAGE_PROCESS_SET_BROWSED_PLAYER:
770                    // Set the new depth.
771                    Log.d(STATE_TAG, "player depth " + msg.arg2);
772                    mBrowseDepth = msg.arg2;
773
774                    // If we already on top of player and there is no content.
775                    // This should very rarely happen.
776                    if (mBrowseDepth == 0 && msg.arg1 == 0) {
777                        broadcastFolderList(mID, mEmptyMediaItemList);
778                        transitionTo(mConnected);
779                    } else {
780                        // Otherwise move to root and fetch the listing.
781                        // the MoveToRoot#enter() function takes care of fetch.
782                        mMoveToRoot.setFolder(mID);
783                        transitionTo(mMoveToRoot);
784                    }
785                    mBrowseTree.setCurrentBrowsedFolder(mID);
786                    // Also set the browsed player here.
787                    mBrowseTree.setCurrentBrowsedPlayer(mID);
788                    break;
789
790                case MESSAGE_INTERNAL_CMD_TIMEOUT:
791                    broadcastFolderList(mID, mEmptyMediaItemList);
792                    transitionTo(mConnected);
793                    break;
794
795                default:
796                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
797                    deferMessage(msg);
798            }
799            return true;
800        }
801    }
802
803    class SetAddresedPlayerAndPlayItem extends CmdState {
804        private String STATE_TAG = "AVRCPSM.SetAddresedPlayerAndPlayItem";
805        int mScope;
806        String mPlayItemId;
807        String mAddrPlayerId;
808
809        public void setItemAndScope(String addrPlayerId, String playItemId, int scope) {
810            mAddrPlayerId = addrPlayerId;
811            mPlayItemId = playItemId;
812            mScope = scope;
813        }
814
815        @Override
816        public boolean processMessage(Message msg) {
817            Log.d(STATE_TAG, "processMessage " + msg);
818            switch (msg.what) {
819                case MESSAGE_PROCESS_SET_ADDRESSED_PLAYER:
820                    // Set the new addressed player.
821                    mBrowseTree.setCurrentAddressedPlayer(mAddrPlayerId);
822
823                    // And now play the item.
824                    AvrcpControllerService.playItemNative(
825                        mRemoteDevice.getBluetoothAddress(), (byte) mScope,
826                        AvrcpControllerService.hexStringToByteUID(mPlayItemId), (int) 0);
827
828                    // Transition to connected state here.
829                    transitionTo(mConnected);
830                    break;
831
832                case MESSAGE_INTERNAL_CMD_TIMEOUT:
833                    transitionTo(mConnected);
834                    break;
835
836                default:
837                    Log.d(STATE_TAG, "deferring message " + msg + " to connected!");
838                    deferMessage(msg);
839            }
840            return true;
841        }
842    }
843
844    // Class template for commands. Each state should do the following:
845    // (a) In enter() send a timeout message which could be tracked in the
846    // processMessage() stage.
847    // (b) In exit() remove all the timeouts.
848    //
849    // Essentially the lifecycle of a timeout should be bounded to a CmdState always.
850    abstract class CmdState extends State {
851        @Override
852        public void enter() {
853            sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
854        }
855
856        @Override
857        public void exit() {
858            removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
859        }
860    }
861
862    // Interface APIs
863    boolean isConnected() {
864        synchronized (mLock) {
865            return mIsConnected;
866        }
867    }
868
869    void doQuit() {
870        try {
871            mContext.unregisterReceiver(mBroadcastReceiver);
872        } catch (IllegalArgumentException expected) {
873            // If the receiver was never registered unregister will throw an
874            // IllegalArgumentException.
875        }
876        quit();
877    }
878
879    void dump(StringBuilder sb) {
880        ProfileService.println(sb, "StateMachine: " + this.toString());
881    }
882
883    MediaMetadata getCurrentMetaData() {
884        synchronized (mLock) {
885            if (mAddressedPlayer != null && mAddressedPlayer.getCurrentTrack() != null) {
886                MediaMetadata mmd = mAddressedPlayer.getCurrentTrack().getMediaMetaData();
887                if (DBG) {
888                    Log.d(TAG, "getCurrentMetaData mmd " + mmd);
889                }
890            }
891            return mEmptyMMD;
892        }
893    }
894
895    PlaybackState getCurrentPlayBackState() {
896        return getCurrentPlayBackState(true);
897    }
898
899    PlaybackState getCurrentPlayBackState(boolean cached) {
900        if (cached) {
901            synchronized (mLock) {
902                if (mAddressedPlayer == null) {
903                    return new PlaybackState.Builder().setState(PlaybackState.STATE_ERROR,
904                        PlaybackState.PLAYBACK_POSITION_UNKNOWN,0).build();
905                }
906                return mAddressedPlayer.getPlaybackState();
907            }
908        } else {
909            // Issue a native request, we return NULL since this is only for PTS.
910            AvrcpControllerService.getPlaybackStateNative(mRemoteDevice.getBluetoothAddress());
911            return null;
912        }
913    }
914
915    // Entry point to the state machine where the services should call to fetch children
916    // for a specific node. It checks if the currently browsed node is the same as the one being
917    // asked for, in that case it returns the currently cached children. This saves bandwidth and
918    // also if we are already fetching elements for a current folder (since we need to batch
919    // fetches) then we should not submit another request but simply return what we have fetched
920    // until now.
921    //
922    // It handles fetches to all VFS, Now Playing and Media Player lists.
923    void getChildren(String parentMediaId, int start, int items) {
924        BrowseTree.BrowseNode bn = mBrowseTree.findBrowseNodeByID(parentMediaId);
925        if (bn == null) {
926            Log.e(TAG, "Invalid folder to browse " + mBrowseTree);
927            broadcastFolderList(parentMediaId, mEmptyMediaItemList);
928            return;
929        }
930
931        if (DBG) {
932            Log.d(TAG, "To Browse folder " + bn + " is cached " + bn.isCached() +
933                " current folder " + mBrowseTree.getCurrentBrowsedFolder());
934        }
935        if (bn.equals(mBrowseTree.getCurrentBrowsedFolder()) && bn.isCached()) {
936            if (DBG) {
937                Log.d(TAG, "Same cached folder -- returning existing children.");
938            }
939            BrowseTree.BrowseNode n = mBrowseTree.findBrowseNodeByID(parentMediaId);
940            ArrayList<MediaItem> childrenList = new ArrayList<MediaItem>();
941            for (BrowseTree.BrowseNode cn : n.getChildren()) {
942                childrenList.add(cn.getMediaItem());
943            }
944            broadcastFolderList(parentMediaId, childrenList);
945            return;
946        }
947
948        Message msg = null;
949        int btDirection = mBrowseTree.getDirection(parentMediaId);
950        BrowseTree.BrowseNode currFol = mBrowseTree.getCurrentBrowsedFolder();
951        if (DBG) {
952            Log.d(TAG, "Browse direction parent " + mBrowseTree.getCurrentBrowsedFolder() +
953                " req " + parentMediaId + " direction " + btDirection);
954        }
955        if (BrowseTree.ROOT.equals(parentMediaId)) {
956            // Root contains the list of players.
957            msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items);
958        } else if (bn.isPlayer() && btDirection != BrowseTree.DIRECTION_SAME) {
959            // Set browsed (and addressed player) as the new player.
960            // This should fetch the list of folders.
961            msg = obtainMessage(AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER,
962                bn.getPlayerID(), 0, bn.getID());
963        } else if (bn.isNowPlaying()) {
964            // Issue a request to fetch the items.
965            msg = obtainMessage(
966                AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST,
967                start, items, parentMediaId);
968        } else {
969            // Only change folder if desired. If an app refreshes a folder
970            // (because it resumed etc) and current folder does not change
971            // then we can simply fetch list.
972
973            // We exempt two conditions from change folder:
974            // a) If the new folder is the same as current folder (refresh of UI)
975            // b) If the new folder is ROOT and current folder is NOW_PLAYING (or vice-versa)
976            // In this condition we 'fake' child-parent hierarchy but it does not exist in
977            // bluetooth world.
978            boolean isNowPlayingToRoot =
979                currFol.isNowPlaying() && bn.getID().equals(BrowseTree.ROOT);
980            if (!isNowPlayingToRoot) {
981                // Find the direction of traversal.
982                int direction = -1;
983                Log.d(TAG, "Browse direction " + currFol + " " + bn + " = " + btDirection);
984                if (btDirection == BrowseTree.DIRECTION_UNKNOWN) {
985                    Log.w(TAG, "parent " + bn + " is not a direct " +
986                        "successor or predeccessor of current folder " + currFol);
987                    broadcastFolderList(parentMediaId, mEmptyMediaItemList);
988                    return;
989                }
990
991                if (btDirection == BrowseTree.DIRECTION_DOWN) {
992                    direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN;
993                } else if (btDirection == BrowseTree.DIRECTION_UP) {
994                    direction = AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP;
995                }
996
997                Bundle b = new Bundle();
998                b.putString(AvrcpControllerService.EXTRA_FOLDER_ID, bn.getID());
999                b.putString(AvrcpControllerService.EXTRA_FOLDER_BT_ID, bn.getFolderUID());
1000                msg = obtainMessage(
1001                    AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH, direction, 0, b);
1002            } else {
1003                // Fetch the listing without changing paths.
1004                msg = obtainMessage(
1005                    AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST,
1006                    start, items, bn.getFolderUID());
1007            }
1008        }
1009
1010        if (msg != null) {
1011            sendMessage(msg);
1012        }
1013    }
1014
1015    public void fetchAttrAndPlayItem(String uid) {
1016        BrowseTree.BrowseNode currItem = mBrowseTree.findFolderByIDLocked(uid);
1017        BrowseTree.BrowseNode currFolder = mBrowseTree.getCurrentBrowsedFolder();
1018        Log.d(TAG, "fetchAttrAndPlayItem mediaId=" + uid + " node=" + currItem);
1019        if (currItem != null) {
1020            int scope = currFolder.isNowPlaying() ?
1021                AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING :
1022                AvrcpControllerService.BROWSE_SCOPE_VFS;
1023            Message msg = obtainMessage(
1024                AvrcpControllerStateMachine.MESSAGE_FETCH_ATTR_AND_PLAY_ITEM,
1025                scope, 0, currItem.getFolderUID());
1026            sendMessage(msg);
1027        }
1028    }
1029
1030    private void broadcastMetaDataChanged(MediaMetadata metadata) {
1031        Intent intent = new Intent(AvrcpControllerService.ACTION_TRACK_EVENT);
1032        intent.putExtra(AvrcpControllerService.EXTRA_METADATA, metadata);
1033        if (DBG) {
1034            Log.d(TAG, " broadcastMetaDataChanged = " + metadata.getDescription());
1035        }
1036        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1037    }
1038
1039    private void broadcastFolderList(String id, ArrayList<MediaItem> items) {
1040        Intent intent = new Intent(AvrcpControllerService.ACTION_FOLDER_LIST);
1041        Log.d(TAG, "broadcastFolderList id " + id + " items " + items);
1042        intent.putExtra(AvrcpControllerService.EXTRA_FOLDER_ID, id);
1043        intent.putParcelableArrayListExtra(
1044            AvrcpControllerService.EXTRA_FOLDER_LIST, items);
1045        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1046    }
1047
1048    private void broadcastPlayBackStateChanged(PlaybackState state) {
1049        Intent intent = new Intent(AvrcpControllerService.ACTION_TRACK_EVENT);
1050        intent.putExtra(AvrcpControllerService.EXTRA_PLAYBACK, state);
1051        if (DBG) {
1052            Log.d(TAG, " broadcastPlayBackStateChanged = " + state.toString());
1053        }
1054        mContext.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
1055    }
1056
1057    private void setAbsVolume(int absVol, int label) {
1058        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
1059        int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
1060        // Ignore first volume command since phone may not know difference between stream volume
1061        // and amplifier volume.
1062        if (mRemoteDevice.getFirstAbsVolCmdRecvd()) {
1063            int newIndex = (maxVolume * absVol) / ABS_VOL_BASE;
1064            Log.d(TAG,
1065                " setAbsVolume =" + absVol + " maxVol = " + maxVolume + " cur = " + currIndex +
1066                    " new = " + newIndex);
1067            /*
1068             * In some cases change in percentage is not sufficient enough to warrant
1069             * change in index values which are in range of 0-15. For such cases
1070             * no action is required
1071             */
1072            if (newIndex != currIndex) {
1073                mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex,
1074                    AudioManager.FLAG_SHOW_UI);
1075            }
1076        } else {
1077            mRemoteDevice.setFirstAbsVolCmdRecvd();
1078            absVol = (currIndex * ABS_VOL_BASE) / maxVolume;
1079            Log.d(TAG, " SetAbsVol recvd for first time, respond with " + absVol);
1080        }
1081        AvrcpControllerService.sendAbsVolRspNative(
1082            mRemoteDevice.getBluetoothAddress(), absVol, label);
1083    }
1084
1085    private int getVolumePercentage() {
1086        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
1087        int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
1088        int percentageVol = ((currIndex * ABS_VOL_BASE) / maxVolume);
1089        return percentageVol;
1090    }
1091
1092    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
1093        @Override
1094        public void onReceive(Context context, Intent intent) {
1095            String action = intent.getAction();
1096            if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
1097                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
1098                if (streamType == AudioManager.STREAM_MUSIC) {
1099                    sendMessage(MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION);
1100                }
1101            }
1102        }
1103    };
1104
1105    public static String dumpMessageString(int message) {
1106        String str = "UNKNOWN";
1107        switch (message) {
1108            case MESSAGE_SEND_PASS_THROUGH_CMD:
1109                str = "REQ_PASS_THROUGH_CMD";
1110                break;
1111            case MESSAGE_SEND_GROUP_NAVIGATION_CMD:
1112                str = "REQ_GRP_NAV_CMD";
1113                break;
1114            case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
1115                str = "CB_SET_ABS_VOL_CMD";
1116                break;
1117            case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
1118                str = "CB_REGISTER_ABS_VOL";
1119                break;
1120            case MESSAGE_PROCESS_TRACK_CHANGED:
1121                str = "CB_TRACK_CHANGED";
1122                break;
1123            case MESSAGE_PROCESS_PLAY_POS_CHANGED:
1124                str = "CB_PLAY_POS_CHANGED";
1125                break;
1126            case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
1127                str = "CB_PLAY_STATUS_CHANGED";
1128                break;
1129            case MESSAGE_PROCESS_RC_FEATURES:
1130                str = "CB_RC_FEATURES";
1131                break;
1132            case MESSAGE_PROCESS_CONNECTION_CHANGE:
1133                str = "CB_CONN_CHANGED";
1134                break;
1135            default:
1136                str = Integer.toString(message);
1137                break;
1138        }
1139        return str;
1140    }
1141
1142    public static String displayBluetoothAvrcpSettings(BluetoothAvrcpPlayerSettings mSett) {
1143        StringBuffer sb =  new StringBuffer();
1144        int supportedSetting = mSett.getSettings();
1145        if(VDBG) Log.d(TAG," setting: " + supportedSetting);
1146        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_EQUALIZER) != 0) {
1147            sb.append(" EQ : ");
1148            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
1149                                                             SETTING_EQUALIZER)));
1150        }
1151        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_REPEAT) != 0) {
1152            sb.append(" REPEAT : ");
1153            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
1154                                                             SETTING_REPEAT)));
1155        }
1156        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SHUFFLE) != 0) {
1157            sb.append(" SHUFFLE : ");
1158            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
1159                                                             SETTING_SHUFFLE)));
1160        }
1161        if((supportedSetting & BluetoothAvrcpPlayerSettings.SETTING_SCAN) != 0) {
1162            sb.append(" SCAN : ");
1163            sb.append(Integer.toString(mSett.getSettingValue(BluetoothAvrcpPlayerSettings.
1164                                                             SETTING_SCAN)));
1165        }
1166        return sb.toString();
1167    }
1168}
1169