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