• 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.BluetoothDevice;
21 import android.bluetooth.BluetoothProfile;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.media.AudioManager;
25 import android.media.MediaMetadata;
26 import android.media.browse.MediaBrowser.MediaItem;
27 import android.media.session.MediaSession;
28 import android.media.session.PlaybackState;
29 import android.os.Bundle;
30 import android.os.Message;
31 import android.util.Log;
32 import android.util.SparseArray;
33 
34 import com.android.bluetooth.BluetoothMetricsProto;
35 import com.android.bluetooth.R;
36 import com.android.bluetooth.Utils;
37 import com.android.bluetooth.a2dpsink.A2dpSinkService;
38 import com.android.bluetooth.btservice.MetricsLogger;
39 import com.android.bluetooth.btservice.ProfileService;
40 import com.android.internal.util.State;
41 import com.android.internal.util.StateMachine;
42 
43 import java.util.ArrayList;
44 import java.util.List;
45 /**
46  * Provides Bluetooth AVRCP Controller State Machine responsible for all remote control connections
47  * and interactions with a remote controlable device.
48  */
49 class AvrcpControllerStateMachine extends StateMachine {
50     static final String TAG = "AvrcpControllerStateMachine";
51     static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
52 
53     //0->99 Events from Outside
54     public static final int CONNECT = 1;
55     public static final int DISCONNECT = 2;
56 
57     //100->199 Internal Events
58     protected static final int CLEANUP = 100;
59     private static final int CONNECT_TIMEOUT = 101;
60 
61     //200->299 Events from Native
62     static final int STACK_EVENT = 200;
63     static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 201;
64 
65     static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 203;
66     static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 204;
67     static final int MESSAGE_PROCESS_TRACK_CHANGED = 205;
68     static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 206;
69     static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 207;
70     static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 208;
71     static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 209;
72     static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE = 210;
73     static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 211;
74     static final int MESSAGE_PROCESS_FOLDER_PATH = 212;
75     static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 213;
76     static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 214;
77     static final int MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED = 215;
78     static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 216;
79 
80     //300->399 Events for Browsing
81     static final int MESSAGE_GET_FOLDER_ITEMS = 300;
82     static final int MESSAGE_PLAY_ITEM = 301;
83     static final int MSG_AVRCP_PASSTHRU = 302;
84 
85     static final int MESSAGE_INTERNAL_ABS_VOL_TIMEOUT = 404;
86 
87     /*
88      * Base value for absolute volume from JNI
89      */
90     private static final int ABS_VOL_BASE = 127;
91 
92     private final AudioManager mAudioManager;
93 
94     protected final BluetoothDevice mDevice;
95     protected final byte[] mDeviceAddress;
96     protected final AvrcpControllerService mService;
97     protected final Disconnected mDisconnected;
98     protected final Connecting mConnecting;
99     protected final Connected mConnected;
100     protected final Disconnecting mDisconnecting;
101 
102     protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED;
103 
104     boolean mRemoteControlConnected = false;
105     boolean mBrowsingConnected = false;
106     final BrowseTree mBrowseTree;
107     private AvrcpPlayer mAddressedPlayer = new AvrcpPlayer();
108     private int mAddressedPlayerId = -1;
109     private SparseArray<AvrcpPlayer> mAvailablePlayerList = new SparseArray<AvrcpPlayer>();
110     private int mVolumeChangedNotificationsToIgnore = 0;
111 
112     GetFolderList mGetFolderList = null;
113 
114     //Number of items to get in a single fetch
115     static final int ITEM_PAGE_SIZE = 20;
116     static final int CMD_TIMEOUT_MILLIS = 10000;
117     static final int ABS_VOL_TIMEOUT_MILLIS = 1000; //1s
118 
AvrcpControllerStateMachine(BluetoothDevice device, AvrcpControllerService service)119     AvrcpControllerStateMachine(BluetoothDevice device, AvrcpControllerService service) {
120         super(TAG);
121         mDevice = device;
122         mDeviceAddress = Utils.getByteAddress(mDevice);
123         mService = service;
124         logD(device.toString());
125 
126         mBrowseTree = new BrowseTree(mDevice);
127         mDisconnected = new Disconnected();
128         mConnecting = new Connecting();
129         mConnected = new Connected();
130         mDisconnecting = new Disconnecting();
131 
132         addState(mDisconnected);
133         addState(mConnecting);
134         addState(mConnected);
135         addState(mDisconnecting);
136 
137         mGetFolderList = new GetFolderList();
138         addState(mGetFolderList, mConnected);
139         mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE);
140 
141         setInitialState(mDisconnected);
142     }
143 
findNode(String parentMediaId)144     BrowseTree.BrowseNode findNode(String parentMediaId) {
145         logD("FindNode");
146         return mBrowseTree.findBrowseNodeByID(parentMediaId);
147     }
148 
149     /**
150      * Get the current connection state
151      *
152      * @return current State
153      */
getState()154     public int getState() {
155         return mMostRecentState;
156     }
157 
158     /**
159      * Get the underlying device tracked by this state machine
160      *
161      * @return device in focus
162      */
getDevice()163     public synchronized BluetoothDevice getDevice() {
164         return mDevice;
165     }
166 
167     /**
168      * send the connection event asynchronously
169      */
connect(StackEvent event)170     public boolean connect(StackEvent event) {
171         if (event.mBrowsingConnected) {
172             onBrowsingConnected();
173         }
174         mRemoteControlConnected = event.mRemoteControlConnected;
175         sendMessage(CONNECT);
176         return true;
177     }
178 
179     /**
180      * send the Disconnect command asynchronously
181      */
disconnect()182     public void disconnect() {
183         sendMessage(DISCONNECT);
184     }
185 
186     /**
187      * Dump the current State Machine to the string builder.
188      *
189      * @param sb output string
190      */
dump(StringBuilder sb)191     public void dump(StringBuilder sb) {
192         ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "("
193                 + mDevice.getName() + ") " + this.toString());
194     }
195 
196     @Override
unhandledMessage(Message msg)197     protected void unhandledMessage(Message msg) {
198         Log.w(TAG, "Unhandled message in state " + getCurrentState() + "msg.what=" + msg.what);
199     }
200 
logD(String message)201     private static void logD(String message) {
202         if (DBG) {
203             Log.d(TAG, message);
204         }
205     }
206 
onBrowsingConnected()207     synchronized void onBrowsingConnected() {
208         if (mBrowsingConnected) return;
209         mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode);
210         BluetoothMediaBrowserService.notifyChanged(mService
211                 .sBrowseTree.mRootNode);
212         BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
213         mBrowsingConnected = true;
214     }
215 
onBrowsingDisconnected()216     synchronized void onBrowsingDisconnected() {
217         if (!mBrowsingConnected) return;
218         mAddressedPlayer.setPlayStatus(PlaybackState.STATE_ERROR);
219         mAddressedPlayer.updateCurrentTrack(null);
220         mBrowseTree.mNowPlayingNode.setCached(false);
221         BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
222         PlaybackState.Builder pbb = new PlaybackState.Builder();
223         pbb.setState(PlaybackState.STATE_ERROR, PlaybackState.PLAYBACK_POSITION_UNKNOWN,
224                 1.0f).setActions(0);
225         pbb.setErrorMessage(mService.getString(R.string.bluetooth_disconnected));
226         BluetoothMediaBrowserService.notifyChanged(pbb.build());
227         mService.sBrowseTree.mRootNode.removeChild(
228                 mBrowseTree.mRootNode);
229         BluetoothMediaBrowserService.notifyChanged(mService
230                 .sBrowseTree.mRootNode);
231         BluetoothMediaBrowserService.trackChanged(null);
232         mBrowsingConnected = false;
233     }
234 
notifyChanged(BrowseTree.BrowseNode node)235     private void notifyChanged(BrowseTree.BrowseNode node) {
236         BluetoothMediaBrowserService.notifyChanged(node);
237     }
238 
requestContents(BrowseTree.BrowseNode node)239     void requestContents(BrowseTree.BrowseNode node) {
240         sendMessage(MESSAGE_GET_FOLDER_ITEMS, node);
241 
242         logD("Fetching " + node);
243     }
244 
nowPlayingContentChanged()245     void nowPlayingContentChanged() {
246         mBrowseTree.mNowPlayingNode.setCached(false);
247         sendMessage(MESSAGE_GET_FOLDER_ITEMS, mBrowseTree.mNowPlayingNode);
248     }
249 
250     protected class Disconnected extends State {
251         @Override
enter()252         public void enter() {
253             logD("Enter Disconnected");
254             if (mMostRecentState != BluetoothProfile.STATE_DISCONNECTED) {
255                 sendMessage(CLEANUP);
256             }
257             broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTED);
258         }
259 
260         @Override
processMessage(Message message)261         public boolean processMessage(Message message) {
262             switch (message.what) {
263                 case CONNECT:
264                     logD("Connect");
265                     transitionTo(mConnecting);
266                     break;
267                 case CLEANUP:
268                     mService.removeStateMachine(AvrcpControllerStateMachine.this);
269                     break;
270             }
271             return true;
272         }
273     }
274 
275     protected class Connecting extends State {
276         @Override
enter()277         public void enter() {
278             logD("Enter Connecting");
279             broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTING);
280             transitionTo(mConnected);
281         }
282     }
283 
284 
285     class Connected extends State {
286         private static final String STATE_TAG = "Avrcp.ConnectedAvrcpController";
287         private int mCurrentlyHeldKey = 0;
288 
289         @Override
enter()290         public void enter() {
291             if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) {
292                 broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
293                 BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
294             } else {
295                 logD("ReEnteringConnected");
296             }
297             super.enter();
298         }
299 
300         @Override
processMessage(Message msg)301         public boolean processMessage(Message msg) {
302             logD(STATE_TAG + " processMessage " + msg.what);
303             switch (msg.what) {
304                 case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
305                     mVolumeChangedNotificationsToIgnore++;
306                     removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
307                     sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
308                             ABS_VOL_TIMEOUT_MILLIS);
309                     setAbsVolume(msg.arg1, msg.arg2);
310                     return true;
311 
312                 case MESSAGE_GET_FOLDER_ITEMS:
313                     transitionTo(mGetFolderList);
314                     return true;
315 
316                 case MESSAGE_PLAY_ITEM:
317                     //Set Addressed Player
318                     playItem((BrowseTree.BrowseNode) msg.obj);
319                     return true;
320 
321                 case MSG_AVRCP_PASSTHRU:
322                     passThru(msg.arg1);
323                     return true;
324 
325                 case MESSAGE_PROCESS_TRACK_CHANGED:
326                     mAddressedPlayer.updateCurrentTrack((MediaMetadata) msg.obj);
327                     BluetoothMediaBrowserService.trackChanged((MediaMetadata) msg.obj);
328                     return true;
329 
330                 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
331                     mAddressedPlayer.setPlayStatus(msg.arg1);
332                     BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
333                     if (mAddressedPlayer.getPlaybackState().getState()
334                             == PlaybackState.STATE_PLAYING
335                             && A2dpSinkService.getFocusState() == AudioManager.AUDIOFOCUS_NONE
336                             && !shouldRequestFocus()) {
337                         sendMessage(MSG_AVRCP_PASSTHRU,
338                                 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
339                     }
340                     return true;
341 
342                 case MESSAGE_PROCESS_PLAY_POS_CHANGED:
343                     if (msg.arg2 != -1) {
344                         mAddressedPlayer.setPlayTime(msg.arg2);
345 
346                         BluetoothMediaBrowserService.notifyChanged(
347                                 mAddressedPlayer.getPlaybackState());
348                     }
349                     return true;
350 
351                 case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED:
352                     mAddressedPlayerId = msg.arg1;
353                     logD("AddressedPlayer = " + mAddressedPlayerId);
354                     AvrcpPlayer updatedPlayer = mAvailablePlayerList.get(mAddressedPlayerId);
355                     if (updatedPlayer != null) {
356                         mAddressedPlayer = updatedPlayer;
357                         logD("AddressedPlayer = " + mAddressedPlayer.getName());
358                     } else {
359                         mBrowseTree.mRootNode.setCached(false);
360                         mBrowseTree.mRootNode.setExpectedChildren(255);
361                         BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode);
362                     }
363                     return true;
364 
365                 case DISCONNECT:
366                     transitionTo(mDisconnecting);
367                     return true;
368 
369                 default:
370                     return super.processMessage(msg);
371             }
372 
373         }
374 
playItem(BrowseTree.BrowseNode node)375         private void playItem(BrowseTree.BrowseNode node) {
376             if (node == null) {
377                 Log.w(TAG, "Invalid item to play");
378             } else {
379                 mService.playItemNative(
380                         mDeviceAddress, node.getScope(),
381                         node.getBluetoothID(), 0);
382             }
383         }
384 
passThru(int cmd)385         private synchronized void passThru(int cmd) {
386             logD("msgPassThru " + cmd);
387             // Some keys should be held until the next event.
388             if (mCurrentlyHeldKey != 0) {
389                 mService.sendPassThroughCommandNative(
390                         mDeviceAddress, mCurrentlyHeldKey,
391                         AvrcpControllerService.KEY_STATE_RELEASED);
392 
393                 if (mCurrentlyHeldKey == cmd) {
394                     // Return to prevent starting FF/FR operation again
395                     mCurrentlyHeldKey = 0;
396                     return;
397                 } else {
398                     // FF/FR is in progress and other operation is desired
399                     // so after stopping FF/FR, not returning so that command
400                     // can be sent for the desired operation.
401                     mCurrentlyHeldKey = 0;
402                 }
403             }
404 
405             // Send the pass through.
406             mService.sendPassThroughCommandNative(mDeviceAddress, cmd,
407                     AvrcpControllerService.KEY_STATE_PRESSED);
408 
409             if (isHoldableKey(cmd)) {
410                 // Release cmd next time a command is sent.
411                 mCurrentlyHeldKey = cmd;
412             } else {
413                 mService.sendPassThroughCommandNative(mDeviceAddress,
414                         cmd, AvrcpControllerService.KEY_STATE_RELEASED);
415             }
416         }
417 
isHoldableKey(int cmd)418         private boolean isHoldableKey(int cmd) {
419             return (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_REWIND)
420                     || (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_FF);
421         }
422     }
423 
424     // Handle the get folder listing action
425     // a) Fetch the listing of folders
426     // b) Once completed return the object listing
427     class GetFolderList extends State {
428         private static final String STATE_TAG = "Avrcp.GetFolderList";
429 
430         boolean mAbort;
431         BrowseTree.BrowseNode mBrowseNode;
432         BrowseTree.BrowseNode mNextStep;
433 
434         @Override
enter()435         public void enter() {
436             logD(STATE_TAG + " Entering GetFolderList");
437             // Setup the timeouts.
438             sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
439             super.enter();
440             mAbort = false;
441             Message msg = getCurrentMessage();
442             if (msg.what == MESSAGE_GET_FOLDER_ITEMS) {
443                 {
444                     logD(STATE_TAG + " new Get Request");
445                     mBrowseNode = (BrowseTree.BrowseNode) msg.obj;
446                 }
447             }
448 
449             if (mBrowseNode == null) {
450                 transitionTo(mConnected);
451             } else {
452                 navigateToFolderOrRetrieve(mBrowseNode);
453             }
454         }
455 
456         @Override
processMessage(Message msg)457         public boolean processMessage(Message msg) {
458             logD(STATE_TAG + " processMessage " + msg.what);
459             switch (msg.what) {
460                 case MESSAGE_PROCESS_GET_FOLDER_ITEMS:
461                     ArrayList<MediaItem> folderList = (ArrayList<MediaItem>) msg.obj;
462                     int endIndicator = mBrowseNode.getExpectedChildren() - 1;
463                     logD("GetFolderItems: End " + endIndicator
464                             + " received " + folderList.size());
465 
466                     // Always update the node so that the user does not wait forever
467                     // for the list to populate.
468                     mBrowseNode.addChildren(folderList);
469                     notifyChanged(mBrowseNode);
470 
471                     if (mBrowseNode.getChildrenCount() >= endIndicator || folderList.size() == 0
472                             || mAbort) {
473                         // If we have fetched all the elements or if the remotes sends us 0 elements
474                         // (which can lead us into a loop since mCurrInd does not proceed) we simply
475                         // abort.
476                         mBrowseNode.setCached(true);
477                         transitionTo(mConnected);
478                     } else {
479                         // Fetch the next set of items.
480                         fetchContents(mBrowseNode);
481                         // Reset the timeout message since we are doing a new fetch now.
482                         removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
483                         sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
484                     }
485                     break;
486                 case MESSAGE_PROCESS_SET_BROWSED_PLAYER:
487                     mBrowseTree.setCurrentBrowsedPlayer(mNextStep.getID(), msg.arg1, msg.arg2);
488                     removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
489                     sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
490                     navigateToFolderOrRetrieve(mBrowseNode);
491                     break;
492 
493                 case MESSAGE_PROCESS_FOLDER_PATH:
494                     mBrowseTree.setCurrentBrowsedFolder(mNextStep.getID());
495                     mBrowseTree.getCurrentBrowsedFolder().setExpectedChildren(msg.arg1);
496 
497                     if (mAbort) {
498                         transitionTo(mConnected);
499                     } else {
500                         removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
501                         sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
502                         navigateToFolderOrRetrieve(mBrowseNode);
503                     }
504                     break;
505 
506                 case MESSAGE_PROCESS_GET_PLAYER_ITEMS:
507                     BrowseTree.BrowseNode rootNode = mBrowseTree.mRootNode;
508                     if (!rootNode.isCached()) {
509                         List<AvrcpPlayer> playerList = (List<AvrcpPlayer>) msg.obj;
510                         mAvailablePlayerList.clear();
511                         for (AvrcpPlayer player : playerList) {
512                             mAvailablePlayerList.put(player.getId(), player);
513                         }
514                         rootNode.addChildren(playerList);
515                         mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT);
516                         rootNode.setExpectedChildren(playerList.size());
517                         rootNode.setCached(true);
518                         notifyChanged(rootNode);
519                     }
520                     transitionTo(mConnected);
521                     break;
522 
523                 case MESSAGE_INTERNAL_CMD_TIMEOUT:
524                     // We have timed out to execute the request, we should simply send
525                     // whatever listing we have gotten until now.
526                     Log.w(TAG, "TIMEOUT");
527                     transitionTo(mConnected);
528                     break;
529 
530                 case MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE:
531                     // If we have gotten an error for OUT OF RANGE we have
532                     // already sent all the items to the client hence simply
533                     // transition to Connected state here.
534                     mBrowseNode.setCached(true);
535                     transitionTo(mConnected);
536                     break;
537 
538                 case MESSAGE_GET_FOLDER_ITEMS:
539                     if (!mBrowseNode.equals(msg.obj)) {
540                         if (shouldAbort(mBrowseNode.getScope(),
541                                  ((BrowseTree.BrowseNode) msg.obj).getScope())) {
542                             mAbort = true;
543                         }
544                         deferMessage(msg);
545                         logD("GetFolderItems: Go Get Another Directory");
546                     } else {
547                         logD("GetFolderItems: Get The Same Directory, ignore");
548                     }
549                     break;
550 
551                 case CONNECT:
552                 case DISCONNECT:
553                 case MSG_AVRCP_PASSTHRU:
554                 case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
555                 case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
556                 case MESSAGE_PROCESS_TRACK_CHANGED:
557                 case MESSAGE_PROCESS_PLAY_POS_CHANGED:
558                 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
559                 case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
560                 case MESSAGE_PLAY_ITEM:
561                 case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED:
562                     // All of these messages should be handled by parent state immediately.
563                     return false;
564 
565                 default:
566                     logD(STATE_TAG + " deferring message " + msg.what
567                                 + " to connected!");
568                     deferMessage(msg);
569             }
570             return true;
571         }
572 
573         /**
574          * shouldAbort calculates the cases where fetching the current directory is no longer
575          * necessary.
576          *
577          * @return true:  a new folder in the same scope
578          *                a new player while fetching contents of a folder
579          *         false: other cases, specifically Now Playing while fetching a folder
580          */
shouldAbort(int currentScope, int fetchScope)581         private boolean shouldAbort(int currentScope, int fetchScope) {
582             if ((currentScope == fetchScope)
583                     || (currentScope == AvrcpControllerService.BROWSE_SCOPE_VFS
584                     && fetchScope == AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST)) {
585                 return true;
586             }
587             return false;
588         }
589 
fetchContents(BrowseTree.BrowseNode target)590         private void fetchContents(BrowseTree.BrowseNode target) {
591             int start = target.getChildrenCount();
592             int end = Math.min(target.getExpectedChildren(), target.getChildrenCount()
593                     + ITEM_PAGE_SIZE) - 1;
594             switch (target.getScope()) {
595                 case AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST:
596                     mService.getPlayerListNative(mDeviceAddress,
597                             start, end);
598                     break;
599                 case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING:
600                     mService.getNowPlayingListNative(
601                             mDeviceAddress, start, end);
602                     break;
603                 case AvrcpControllerService.BROWSE_SCOPE_VFS:
604                     mService.getFolderListNative(mDeviceAddress,
605                             start, end);
606                     break;
607                 default:
608                     Log.e(TAG, STATE_TAG + " Scope " + target.getScope()
609                             + " cannot be handled here.");
610             }
611         }
612 
613         /* One of several things can happen when trying to get a folder list
614          *
615          *
616          * 0: The folder handle is no longer valid
617          * 1: The folder contents can be retrieved directly (NowPlaying, Root, Current)
618          * 2: The folder is a browsable player
619          * 3: The folder is a non browsable player
620          * 4: The folder is not a child of the current folder
621          * 5: The folder is a child of the current folder
622          *
623          */
navigateToFolderOrRetrieve(BrowseTree.BrowseNode target)624         private void navigateToFolderOrRetrieve(BrowseTree.BrowseNode target) {
625             mNextStep = mBrowseTree.getNextStepToFolder(target);
626             logD("NAVIGATING From "
627                     + mBrowseTree.getCurrentBrowsedFolder().toString());
628             logD("NAVIGATING Toward " + target.toString());
629             if (mNextStep == null) {
630                 return;
631             } else if (target.equals(mBrowseTree.mNowPlayingNode)
632                     || target.equals(mBrowseTree.mRootNode)
633                     || mNextStep.equals(mBrowseTree.getCurrentBrowsedFolder())) {
634                 fetchContents(mNextStep);
635             } else if (mNextStep.isPlayer()) {
636                 logD("NAVIGATING Player " + mNextStep.toString());
637                 if (mNextStep.isBrowsable()) {
638                     mService.setBrowsedPlayerNative(
639                             mDeviceAddress, (int) mNextStep.getBluetoothID());
640                 } else {
641                     logD("Player doesn't support browsing");
642                     mNextStep.setCached(true);
643                     transitionTo(mConnected);
644                 }
645             } else if (mNextStep.equals(mBrowseTree.mNavigateUpNode)) {
646                 logD("NAVIGATING UP " + mNextStep.toString());
647                 mNextStep = mBrowseTree.getCurrentBrowsedFolder().getParent();
648                 mBrowseTree.getCurrentBrowsedFolder().setCached(false);
649 
650                 mService.changeFolderPathNative(
651                         mDeviceAddress,
652                         AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP,
653                         0);
654 
655             } else {
656                 logD("NAVIGATING DOWN " + mNextStep.toString());
657                 mService.changeFolderPathNative(
658                         mDeviceAddress,
659                         AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN,
660                         mNextStep.getBluetoothID());
661             }
662         }
663 
664         @Override
exit()665         public void exit() {
666             removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
667             mBrowseNode = null;
668             super.exit();
669         }
670     }
671 
672     protected class Disconnecting extends State {
673         @Override
enter()674         public void enter() {
675             onBrowsingDisconnected();
676             broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING);
677             transitionTo(mDisconnected);
678         }
679     }
680 
681 
setAbsVolume(int absVol, int label)682     private void setAbsVolume(int absVol, int label) {
683         int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
684         int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
685         int newIndex = (maxVolume * absVol) / ABS_VOL_BASE;
686         logD(" setAbsVolume =" + absVol + " maxVol = " + maxVolume
687                 + " cur = " + currIndex + " new = " + newIndex);
688         /*
689          * In some cases change in percentage is not sufficient enough to warrant
690          * change in index values which are in range of 0-15. For such cases
691          * no action is required
692          */
693         if (newIndex != currIndex) {
694             mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newIndex,
695                     AudioManager.FLAG_SHOW_UI);
696         }
697         mService.sendAbsVolRspNative(mDeviceAddress, absVol, label);
698     }
699 
700     MediaSession.Callback mSessionCallbacks = new MediaSession.Callback() {
701         @Override
702         public void onPlay() {
703             logD("onPlay");
704             onPrepare();
705             sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PLAY);
706         }
707 
708         @Override
709         public void onPause() {
710             logD("onPause");
711             sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
712         }
713 
714         @Override
715         public void onSkipToNext() {
716             logD("onSkipToNext");
717             onPrepare();
718             sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD);
719         }
720 
721         @Override
722         public void onSkipToPrevious() {
723             logD("onSkipToPrevious");
724             onPrepare();
725             sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD);
726         }
727 
728         @Override
729         public void onSkipToQueueItem(long id) {
730             logD("onSkipToQueueItem" + id);
731             onPrepare();
732             BrowseTree.BrowseNode node = mBrowseTree.getTrackFromNowPlayingList((int) id);
733             if (node != null) {
734                 sendMessage(MESSAGE_PLAY_ITEM, node);
735             }
736         }
737 
738         @Override
739         public void onStop() {
740             logD("onStop");
741             sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_STOP);
742         }
743 
744         @Override
745         public void onPrepare() {
746             logD("onPrepare");
747             A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
748             if (a2dpSinkService != null) {
749                 a2dpSinkService.requestAudioFocus(mDevice, true);
750             }
751         }
752 
753         @Override
754         public void onRewind() {
755             logD("onRewind");
756             sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_REWIND);
757         }
758 
759         @Override
760         public void onFastForward() {
761             logD("onFastForward");
762             sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FF);
763         }
764 
765         @Override
766         public void onPlayFromMediaId(String mediaId, Bundle extras) {
767             logD("onPlayFromMediaId");
768             // Play the item if possible.
769             onPrepare();
770             BrowseTree.BrowseNode node = mBrowseTree.findBrowseNodeByID(mediaId);
771             sendMessage(MESSAGE_PLAY_ITEM, node);
772         }
773     };
774 
broadcastConnectionStateChanged(int currentState)775     protected void broadcastConnectionStateChanged(int currentState) {
776         if (mMostRecentState == currentState) {
777             return;
778         }
779         if (currentState == BluetoothProfile.STATE_CONNECTED) {
780             MetricsLogger.logProfileConnectionEvent(
781                     BluetoothMetricsProto.ProfileId.AVRCP_CONTROLLER);
782         }
783         logD("Connection state " + mDevice + ": " + mMostRecentState + "->" + currentState);
784         Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
785         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, mMostRecentState);
786         intent.putExtra(BluetoothProfile.EXTRA_STATE, currentState);
787         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
788         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
789         mMostRecentState = currentState;
790         mService.sendBroadcast(intent, ProfileService.BLUETOOTH_PERM);
791     }
792 
shouldRequestFocus()793     private boolean shouldRequestFocus() {
794         return mService.getResources()
795                 .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus);
796     }
797 }
798