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