• 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 static android.Manifest.permission.BLUETOOTH_CONNECT;
20 
21 import android.bluetooth.BluetoothAvrcpController;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothProfile;
24 import android.content.Intent;
25 import android.media.AudioManager;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.os.Message;
29 import android.support.v4.media.MediaBrowserCompat.MediaItem;
30 import android.support.v4.media.session.MediaSessionCompat;
31 import android.support.v4.media.session.PlaybackStateCompat;
32 import android.util.Log;
33 import android.util.SparseArray;
34 
35 import com.android.bluetooth.BluetoothMetricsProto;
36 import com.android.bluetooth.R;
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.annotations.VisibleForTesting;
42 import com.android.internal.util.State;
43 import com.android.internal.util.StateMachine;
44 
45 import java.util.ArrayList;
46 import java.util.List;
47 import java.util.Set;
48 
49 /**
50  * Provides Bluetooth AVRCP Controller State Machine responsible for all remote control connections
51  * and interactions with a remote controlable device.
52  */
53 class AvrcpControllerStateMachine extends StateMachine {
54     static final String TAG = "AvrcpControllerStateMachine";
55     static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
56 
57     //0->99 Events from Outside
58     public static final int CONNECT = 1;
59     public static final int DISCONNECT = 2;
60     public static final int ACTIVE_DEVICE_CHANGE = 3;
61     public static final int AUDIO_FOCUS_STATE_CHANGE = 4;
62 
63     //100->199 Internal Events
64     protected static final int CLEANUP = 100;
65     private static final int CONNECT_TIMEOUT = 101;
66     static final int MESSAGE_INTERNAL_ABS_VOL_TIMEOUT = 102;
67 
68     //200->299 Events from Native
69     static final int STACK_EVENT = 200;
70     static final int MESSAGE_INTERNAL_CMD_TIMEOUT = 201;
71 
72     static final int MESSAGE_PROCESS_SET_ABS_VOL_CMD = 203;
73     static final int MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION = 204;
74     static final int MESSAGE_PROCESS_TRACK_CHANGED = 205;
75     static final int MESSAGE_PROCESS_PLAY_POS_CHANGED = 206;
76     static final int MESSAGE_PROCESS_PLAY_STATUS_CHANGED = 207;
77     static final int MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION = 208;
78     static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS = 209;
79     static final int MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE = 210;
80     static final int MESSAGE_PROCESS_GET_PLAYER_ITEMS = 211;
81     static final int MESSAGE_PROCESS_FOLDER_PATH = 212;
82     static final int MESSAGE_PROCESS_SET_BROWSED_PLAYER = 213;
83     static final int MESSAGE_PROCESS_SET_ADDRESSED_PLAYER = 214;
84     static final int MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED = 215;
85     static final int MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED = 216;
86     static final int MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS = 217;
87     static final int MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS = 218;
88     static final int MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED = 219;
89     static final int MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM = 220;
90 
91     //300->399 Events for Browsing
92     static final int MESSAGE_GET_FOLDER_ITEMS = 300;
93     static final int MESSAGE_PLAY_ITEM = 301;
94     static final int MSG_AVRCP_PASSTHRU = 302;
95     static final int MSG_AVRCP_SET_SHUFFLE = 303;
96     static final int MSG_AVRCP_SET_REPEAT = 304;
97 
98     //400->499 Events for Cover Artwork
99     static final int MESSAGE_PROCESS_IMAGE_DOWNLOADED = 400;
100 
101     /*
102      * Base value for absolute volume from JNI
103      */
104     private static final int ABS_VOL_BASE = 127;
105 
106     /*
107      * Notification types for Avrcp protocol JNI.
108      */
109     private static final byte NOTIFICATION_RSP_TYPE_INTERIM = 0x00;
110     private static final byte NOTIFICATION_RSP_TYPE_CHANGED = 0x01;
111 
112     private static BluetoothDevice sActiveDevice;
113     private final AudioManager mAudioManager;
114     private boolean mShouldSendPlayOnFocusRecovery = false;
115     private final boolean mIsVolumeFixed;
116 
117     protected final BluetoothDevice mDevice;
118     protected final byte[] mDeviceAddress;
119     protected final AvrcpControllerService mService;
120     protected int mCoverArtPsm;
121     protected final AvrcpCoverArtManager mCoverArtManager;
122     protected final Disconnected mDisconnected;
123     protected final Connecting mConnecting;
124     protected final Connected mConnected;
125     protected final Disconnecting mDisconnecting;
126 
127     protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED;
128 
129     boolean mRemoteControlConnected = false;
130     boolean mBrowsingConnected = false;
131     final BrowseTree mBrowseTree;
132 
133     private AvrcpPlayer mAddressedPlayer;
134     private int mAddressedPlayerId;
135     private SparseArray<AvrcpPlayer> mAvailablePlayerList;
136 
137     private int mVolumeChangedNotificationsToIgnore = 0;
138     private int mVolumeNotificationLabel = -1;
139 
140     GetFolderList mGetFolderList = null;
141 
142     //Number of items to get in a single fetch
143     static final int ITEM_PAGE_SIZE = 20;
144     static final int CMD_TIMEOUT_MILLIS = 10000;
145     static final int ABS_VOL_TIMEOUT_MILLIS = 1000; //1s
146 
AvrcpControllerStateMachine(BluetoothDevice device, AvrcpControllerService service)147     AvrcpControllerStateMachine(BluetoothDevice device, AvrcpControllerService service) {
148         super(TAG);
149         mDevice = device;
150         mDeviceAddress = Utils.getByteAddress(mDevice);
151         mService = service;
152         mCoverArtPsm = 0;
153         mCoverArtManager = service.getCoverArtManager();
154         logD(device.toString());
155 
156         mAvailablePlayerList = new SparseArray<AvrcpPlayer>();
157         mAddressedPlayerId = AvrcpPlayer.DEFAULT_ID;
158 
159         AvrcpPlayer.Builder apb = new AvrcpPlayer.Builder();
160         apb.setDevice(mDevice);
161         apb.setPlayerId(mAddressedPlayerId);
162         apb.setSupportedFeature(AvrcpPlayer.FEATURE_PLAY);
163         apb.setSupportedFeature(AvrcpPlayer.FEATURE_PAUSE);
164         apb.setSupportedFeature(AvrcpPlayer.FEATURE_STOP);
165         apb.setSupportedFeature(AvrcpPlayer.FEATURE_FORWARD);
166         apb.setSupportedFeature(AvrcpPlayer.FEATURE_PREVIOUS);
167         mAddressedPlayer = apb.build();
168         mAvailablePlayerList.put(mAddressedPlayerId, mAddressedPlayer);
169 
170         mBrowseTree = new BrowseTree(mDevice);
171         mDisconnected = new Disconnected();
172         mConnecting = new Connecting();
173         mConnected = new Connected();
174         mDisconnecting = new Disconnecting();
175 
176         addState(mDisconnected);
177         addState(mConnecting);
178         addState(mConnected);
179         addState(mDisconnecting);
180 
181         mGetFolderList = new GetFolderList();
182         addState(mGetFolderList, mConnected);
183         mAudioManager = service.getSystemService(AudioManager.class);
184         mIsVolumeFixed = mAudioManager.isVolumeFixed();
185 
186         setInitialState(mDisconnected);
187     }
188 
findNode(String parentMediaId)189     BrowseTree.BrowseNode findNode(String parentMediaId) {
190         logD("findNode(device=" + mDevice + ", mediaId=" + parentMediaId + ")");
191         return mBrowseTree.findBrowseNodeByID(parentMediaId);
192     }
193 
194     /**
195      * Get the current connection state
196      *
197      * @return current State
198      */
getState()199     public int getState() {
200         return mMostRecentState;
201     }
202 
203     /**
204      * Get the underlying device tracked by this state machine
205      *
206      * @return device in focus
207      */
getDevice()208     public BluetoothDevice getDevice() {
209         return mDevice;
210     }
211 
212     /**
213      * send the connection event asynchronously
214      */
connect(StackEvent event)215     public boolean connect(StackEvent event) {
216         if (event.mBrowsingConnected) {
217             onBrowsingConnected();
218         }
219         mRemoteControlConnected = event.mRemoteControlConnected;
220         sendMessage(CONNECT);
221         return true;
222     }
223 
224     /**
225      * send the Disconnect command asynchronously
226      */
disconnect()227     public void disconnect() {
228         sendMessage(DISCONNECT);
229     }
230 
231     /**
232      * Get the current playing track
233      */
getCurrentTrack()234     public AvrcpItem getCurrentTrack() {
235         return mAddressedPlayer.getCurrentTrack();
236     }
237 
238     @VisibleForTesting
getAddressedPlayerId()239     int getAddressedPlayerId() {
240         return mAddressedPlayerId;
241     }
242 
243     @VisibleForTesting
getAvailablePlayers()244     SparseArray<AvrcpPlayer> getAvailablePlayers() {
245         return mAvailablePlayerList;
246     }
247 
248     /**
249      * Dump the current State Machine to the string builder.
250      *
251      * @param sb output string
252      */
dump(StringBuilder sb)253     public void dump(StringBuilder sb) {
254         ProfileService.println(sb, "mDevice: " + mDevice + "("
255                 + Utils.getName(mDevice) + ") " + this.toString());
256         ProfileService.println(sb, "isActive: " + isActive());
257         ProfileService.println(sb, "Control: " + mRemoteControlConnected);
258         ProfileService.println(sb, "Browsing: " + mBrowsingConnected);
259         ProfileService.println(sb, "Cover Art: "
260                 + (mCoverArtManager.getState(mDevice) == BluetoothProfile.STATE_CONNECTED));
261 
262         ProfileService.println(sb, "Addressed Player ID: " + mAddressedPlayerId);
263         ProfileService.println(sb, "Available Players (" + mAvailablePlayerList.size() + "): ");
264         for (int i = 0; i < mAvailablePlayerList.size(); i++) {
265             AvrcpPlayer player = mAvailablePlayerList.valueAt(i);
266             boolean isAddressed = (player.getId() == mAddressedPlayerId);
267             ProfileService.println(sb, "\t" + (isAddressed ? "(Addressed) " : "") + player);
268         }
269 
270         List<MediaItem> queue = null;
271         if (mBrowseTree.mNowPlayingNode != null) {
272             queue = mBrowseTree.mNowPlayingNode.getContents();
273         }
274         ProfileService.println(sb, "Queue (" + (queue == null ? 0 : queue.size()) + "): " + queue);
275     }
276 
277     @VisibleForTesting
isActive()278     boolean isActive() {
279         return mDevice.equals(mService.getActiveDevice());
280     }
281 
282     /**
283      * Attempt to set the active status for this device
284      */
setDeviceState(int state)285     public void setDeviceState(int state) {
286         sendMessage(ACTIVE_DEVICE_CHANGE, state);
287     }
288 
289     @Override
unhandledMessage(Message msg)290     protected void unhandledMessage(Message msg) {
291         Log.w(TAG, "Unhandled message in state " + getCurrentState() + "msg.what="
292                 + eventToString(msg.what));
293     }
294 
logD(String message)295     private static void logD(String message) {
296         if (DBG) {
297             Log.d(TAG, message);
298         }
299     }
300 
onBrowsingConnected()301     synchronized void onBrowsingConnected() {
302         mBrowsingConnected = true;
303         requestContents(mBrowseTree.mRootNode);
304     }
305 
onBrowsingDisconnected()306     synchronized void onBrowsingDisconnected() {
307         if (!mBrowsingConnected) return;
308         mAddressedPlayer.setPlayStatus(PlaybackStateCompat.STATE_ERROR);
309         AvrcpItem previousTrack = mAddressedPlayer.getCurrentTrack();
310         String previousTrackUuid = previousTrack != null ? previousTrack.getCoverArtUuid() : null;
311         mAddressedPlayer.updateCurrentTrack(null);
312         mBrowseTree.mNowPlayingNode.setCached(false);
313         mBrowseTree.mRootNode.setCached(false);
314         if (isActive()) {
315             BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
316             BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode);
317         }
318         removeUnusedArtwork(previousTrackUuid);
319         removeUnusedArtworkFromBrowseTree();
320         mBrowsingConnected = false;
321     }
322 
connectCoverArt()323     synchronized void connectCoverArt() {
324         // Called from "connected" state, which assumes either control or browse is connected
325         if (mCoverArtManager != null && mCoverArtPsm != 0
326                 && mCoverArtManager.getState(mDevice) != BluetoothProfile.STATE_CONNECTED) {
327             logD("Attempting to connect to AVRCP BIP, psm: " + mCoverArtPsm);
328             mCoverArtManager.connect(mDevice, /* psm */ mCoverArtPsm);
329         }
330     }
331 
refreshCoverArt()332     synchronized void refreshCoverArt() {
333         if (mCoverArtManager != null && mCoverArtPsm != 0
334                 && mCoverArtManager.getState(mDevice) == BluetoothProfile.STATE_CONNECTED) {
335             logD("Attempting to refresh AVRCP BIP OBEX session, psm: " + mCoverArtPsm);
336             mCoverArtManager.refreshSession(mDevice);
337         }
338     }
339 
disconnectCoverArt()340     synchronized void disconnectCoverArt() {
341         // Safe to call even if we're not connected
342         if (mCoverArtManager != null) {
343             logD("Disconnect BIP cover artwork");
344             mCoverArtManager.disconnect(mDevice);
345         }
346     }
347 
348     /**
349      * Remove an unused cover art image from storage if it's unused by the browse tree and the
350      * current track.
351      */
removeUnusedArtwork(String previousTrackUuid)352     synchronized void removeUnusedArtwork(String previousTrackUuid) {
353         logD("removeUnusedArtwork(" + previousTrackUuid + ")");
354         if (mCoverArtManager == null) return;
355         AvrcpItem currentTrack = getCurrentTrack();
356         String currentTrackUuid = currentTrack != null ? currentTrack.getCoverArtUuid() : null;
357         if (previousTrackUuid != null) {
358             if (!previousTrackUuid.equals(currentTrackUuid)
359                     && mBrowseTree.getNodesUsingCoverArt(previousTrackUuid).isEmpty()) {
360                 mCoverArtManager.removeImage(mDevice, previousTrackUuid);
361             }
362         }
363     }
364 
365     /**
366      * Queries the browse tree for unused uuids and removes the associated images from storage
367      * if the uuid is not used by the current track.
368      */
removeUnusedArtworkFromBrowseTree()369     synchronized void removeUnusedArtworkFromBrowseTree() {
370         logD("removeUnusedArtworkFromBrowseTree()");
371         if (mCoverArtManager == null) return;
372         AvrcpItem currentTrack = getCurrentTrack();
373         String currentTrackUuid = currentTrack != null ? currentTrack.getCoverArtUuid() : null;
374         ArrayList<String> unusedArtwork = mBrowseTree.getAndClearUnusedCoverArt();
375         for (String uuid : unusedArtwork) {
376             if (!uuid.equals(currentTrackUuid)) {
377                 mCoverArtManager.removeImage(mDevice, uuid);
378             }
379         }
380     }
381 
notifyChanged(BrowseTree.BrowseNode node)382     private void notifyChanged(BrowseTree.BrowseNode node) {
383         // We should only notify now playing content updates if we're the active device. VFS
384         // updates are fine at any time
385         int scope = node.getScope();
386         if (scope != AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING
387                 || (scope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING
388                 && isActive())) {
389             BluetoothMediaBrowserService.notifyChanged(node);
390         }
391     }
392 
notifyChanged(PlaybackStateCompat state)393     private void notifyChanged(PlaybackStateCompat state) {
394         if (isActive()) {
395             BluetoothMediaBrowserService.notifyChanged(state);
396         }
397     }
398 
requestContents(BrowseTree.BrowseNode node)399     void requestContents(BrowseTree.BrowseNode node) {
400         sendMessage(MESSAGE_GET_FOLDER_ITEMS, node);
401         logD("Fetching " + node);
402     }
403 
playItem(BrowseTree.BrowseNode node)404     public void playItem(BrowseTree.BrowseNode node) {
405         sendMessage(MESSAGE_PLAY_ITEM, node);
406     }
407 
nowPlayingContentChanged()408     void nowPlayingContentChanged() {
409         removeUnusedArtworkFromBrowseTree();
410         requestContents(mBrowseTree.mNowPlayingNode);
411     }
412 
413     protected class Disconnected extends State {
414         @Override
enter()415         public void enter() {
416             logD("Enter Disconnected");
417             if (mMostRecentState != BluetoothProfile.STATE_DISCONNECTED) {
418                 sendMessage(CLEANUP);
419             }
420             broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTED);
421         }
422 
423         @Override
processMessage(Message message)424         public boolean processMessage(Message message) {
425             switch (message.what) {
426                 case MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM:
427                     mCoverArtPsm = message.arg1;
428                     break;
429                 case CONNECT:
430                     logD("Connect");
431                     transitionTo(mConnecting);
432                     break;
433                 case CLEANUP:
434                     mService.removeStateMachine(AvrcpControllerStateMachine.this);
435                     break;
436                 case ACTIVE_DEVICE_CHANGE:
437                     // Wait until we're connected to process this
438                     deferMessage(message);
439                     break;
440             }
441             return true;
442         }
443     }
444 
445     protected class Connecting extends State {
446         @Override
enter()447         public void enter() {
448             logD("Enter Connecting");
449             broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTING);
450             transitionTo(mConnected);
451         }
452     }
453 
454 
455     class Connected extends State {
456         private static final String STATE_TAG = "Avrcp.ConnectedAvrcpController";
457         private int mCurrentlyHeldKey = 0;
458 
459         @Override
enter()460         public void enter() {
461             if (mMostRecentState == BluetoothProfile.STATE_CONNECTING) {
462                 broadcastConnectionStateChanged(BluetoothProfile.STATE_CONNECTED);
463                 mService.sBrowseTree.mRootNode.addChild(mBrowseTree.mRootNode);
464                 BluetoothMediaBrowserService.notifyChanged(mService.sBrowseTree.mRootNode);
465                 connectCoverArt(); // only works if we have a valid PSM
466             } else {
467                 logD("ReEnteringConnected");
468             }
469             super.enter();
470         }
471 
472         @Override
processMessage(Message msg)473         public boolean processMessage(Message msg) {
474             logD(STATE_TAG + " processMessage " + eventToString(msg.what));
475             switch (msg.what) {
476                 case ACTIVE_DEVICE_CHANGE:
477                     int state = msg.arg1;
478                     if (state == AvrcpControllerService.DEVICE_STATE_ACTIVE) {
479                         BluetoothMediaBrowserService.addressedPlayerChanged(mSessionCallbacks);
480                         BluetoothMediaBrowserService.trackChanged(
481                                 mAddressedPlayer.getCurrentTrack());
482                         BluetoothMediaBrowserService.notifyChanged(
483                                 mAddressedPlayer.getPlaybackState());
484                         BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
485 
486                         // If we switch to a device that is playing and we don't have focus, pause
487                         int focusState = getFocusState();
488                         if (mAddressedPlayer.getPlaybackState().getState()
489                                 == PlaybackStateCompat.STATE_PLAYING
490                                 && focusState == AudioManager.AUDIOFOCUS_NONE) {
491                             sendMessage(MSG_AVRCP_PASSTHRU,
492                                     AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
493                         }
494                     } else {
495                         sendMessage(MSG_AVRCP_PASSTHRU,
496                                 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
497                         mShouldSendPlayOnFocusRecovery = false;
498                     }
499                     return true;
500 
501                 case AUDIO_FOCUS_STATE_CHANGE:
502                     int newState = msg.arg1;
503                     logD("Audio focus changed -> " + newState);
504                     switch (newState) {
505                         case AudioManager.AUDIOFOCUS_GAIN:
506                             // Begin playing audio again if we paused the remote
507                             if (mShouldSendPlayOnFocusRecovery) {
508                                 logD("Regained focus, establishing play status");
509                                 sendMessage(MSG_AVRCP_PASSTHRU,
510                                         AvrcpControllerService.PASS_THRU_CMD_ID_PLAY);
511                             }
512                             mShouldSendPlayOnFocusRecovery = false;
513                             break;
514 
515                         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
516                             // Temporary loss of focus. Send a courtesy pause if we are playing and
517                             // note we should recover
518                             if (mAddressedPlayer.getPlaybackState().getState()
519                                     == PlaybackStateCompat.STATE_PLAYING) {
520                                 logD("Transient loss, temporarily pause with intent to recover");
521                                 sendMessage(MSG_AVRCP_PASSTHRU,
522                                         AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
523                                 mShouldSendPlayOnFocusRecovery = true;
524                             }
525                             break;
526 
527                         case AudioManager.AUDIOFOCUS_LOSS:
528                             // Permanent loss of focus probably due to another audio app. Send a
529                             // courtesy pause
530                             logD("Lost focus, send a courtesy pause");
531                             if (mAddressedPlayer.getPlaybackState().getState()
532                                     == PlaybackStateCompat.STATE_PLAYING) {
533                                 sendMessage(MSG_AVRCP_PASSTHRU,
534                                         AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
535                             }
536                             mShouldSendPlayOnFocusRecovery = false;
537                             break;
538                     }
539                     return true;
540 
541                 case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
542                     mVolumeChangedNotificationsToIgnore++;
543                     removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
544                     sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
545                             ABS_VOL_TIMEOUT_MILLIS);
546                     handleAbsVolumeRequest(msg.arg1, msg.arg2);
547                     return true;
548 
549                 case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
550                     mVolumeNotificationLabel = msg.arg1;
551                     mService.sendRegisterAbsVolRspNative(mDeviceAddress,
552                             NOTIFICATION_RSP_TYPE_INTERIM,
553                             getAbsVolume(), mVolumeNotificationLabel);
554                     return true;
555 
556                 case MESSAGE_GET_FOLDER_ITEMS:
557                     transitionTo(mGetFolderList);
558                     return true;
559 
560                 case MESSAGE_PLAY_ITEM:
561                     //Set Addressed Player
562                     processPlayItem((BrowseTree.BrowseNode) msg.obj);
563                     return true;
564 
565                 case MSG_AVRCP_PASSTHRU:
566                     passThru(msg.arg1);
567                     return true;
568 
569                 case MSG_AVRCP_SET_REPEAT:
570                     setRepeat(msg.arg1);
571                     return true;
572 
573                 case MSG_AVRCP_SET_SHUFFLE:
574                     setShuffle(msg.arg1);
575                     return true;
576 
577                 case MESSAGE_PROCESS_TRACK_CHANGED:
578                     AvrcpItem track = (AvrcpItem) msg.obj;
579                     AvrcpItem previousTrack = mAddressedPlayer.getCurrentTrack();
580                     downloadImageIfNeeded(track);
581                     mAddressedPlayer.updateCurrentTrack(track);
582                     if (isActive()) {
583                         BluetoothMediaBrowserService.trackChanged(track);
584                     }
585                     if (previousTrack != null) {
586                         removeUnusedArtwork(previousTrack.getCoverArtUuid());
587                         removeUnusedArtworkFromBrowseTree();
588                     }
589                     return true;
590 
591                 case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
592                     logd("Playback status changed to " + msg.arg1);
593                     mAddressedPlayer.setPlayStatus(msg.arg1);
594                     if (!isActive()) {
595                         sendMessage(MSG_AVRCP_PASSTHRU,
596                                 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
597                         return true;
598                     }
599 
600                     BluetoothMediaBrowserService.notifyChanged(mAddressedPlayer.getPlaybackState());
601 
602                     int focusState = getFocusState();
603                     if (focusState == AudioManager.ERROR) {
604                         sendMessage(MSG_AVRCP_PASSTHRU,
605                                 AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
606                         return true;
607                     }
608 
609                     if (mAddressedPlayer.getPlaybackState().getState()
610                             == PlaybackStateCompat.STATE_PLAYING
611                             && focusState == AudioManager.AUDIOFOCUS_NONE) {
612                         if (shouldRequestFocus()) {
613                             mSessionCallbacks.onPrepare();
614                         } else {
615                             sendMessage(MSG_AVRCP_PASSTHRU,
616                                     AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
617                         }
618                     }
619                     return true;
620 
621                 case MESSAGE_PROCESS_PLAY_POS_CHANGED:
622                     if (msg.arg2 != -1) {
623                         mAddressedPlayer.setPlayTime(msg.arg2);
624                         notifyChanged(mAddressedPlayer.getPlaybackState());
625                     }
626                     return true;
627 
628                 case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED:
629                     int oldAddressedPlayerId = mAddressedPlayerId;
630                     mAddressedPlayerId = msg.arg1;
631                     logD("AddressedPlayer changed " + oldAddressedPlayerId + " -> "
632                             + mAddressedPlayerId);
633 
634                     // The now playing list is tied to the addressed player by specification in
635                     // AVRCP 5.9.1. A new addressed player means our now playing content is now
636                     // invalid
637                     mBrowseTree.mNowPlayingNode.setCached(false);
638                     if (isActive()) {
639                         logD("Addressed player change has invalidated the now playing list");
640                         BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mNowPlayingNode);
641                     }
642                     removeUnusedArtworkFromBrowseTree();
643 
644                     // For devices that support browsing, we *may* have an AvrcpPlayer with player
645                     // metadata already. We could also be in the middle fetching it. If the player
646                     // isn't there then we need to ensure that a default Addressed AvrcpPlayer is
647                     // created to represent it. It can be updated if/when we do fetch the player.
648                     if (!mAvailablePlayerList.contains(mAddressedPlayerId)) {
649                         logD("Available player set does not contain the new Addressed Player");
650                         AvrcpPlayer.Builder apb = new AvrcpPlayer.Builder();
651                         apb.setDevice(mDevice);
652                         apb.setPlayerId(mAddressedPlayerId);
653                         apb.setSupportedFeature(AvrcpPlayer.FEATURE_PLAY);
654                         apb.setSupportedFeature(AvrcpPlayer.FEATURE_PAUSE);
655                         apb.setSupportedFeature(AvrcpPlayer.FEATURE_STOP);
656                         apb.setSupportedFeature(AvrcpPlayer.FEATURE_FORWARD);
657                         apb.setSupportedFeature(AvrcpPlayer.FEATURE_PREVIOUS);
658                         mAvailablePlayerList.put(mAddressedPlayerId, apb.build());
659                     }
660 
661                     // Set our new addressed player object from our set of available players that's
662                     // guaranteed to have the addressed player now.
663                     mAddressedPlayer = mAvailablePlayerList.get(mAddressedPlayerId);
664 
665                     // Fetch metadata including the now playing list. The specification claims that
666                     // the player feature bit only incidates if the player *natively* supports a now
667                     // playing list. However, now playing is mandatory if browsing is supported,
668                     // even if the player doesn't support it. A list of one item can be returned
669                     // instead.
670                     mService.getCurrentMetadataNative(Utils.getByteAddress(mDevice));
671                     mService.getPlaybackStateNative(Utils.getByteAddress(mDevice));
672                     requestContents(mBrowseTree.mNowPlayingNode);
673                     logD("AddressedPlayer = " + mAddressedPlayer);
674                     return true;
675 
676                 case MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS:
677                     mAddressedPlayer.setSupportedPlayerApplicationSettings(
678                             (PlayerApplicationSettings) msg.obj);
679                     notifyChanged(mAddressedPlayer.getPlaybackState());
680                     return true;
681 
682                 case MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS:
683                     mAddressedPlayer.setCurrentPlayerApplicationSettings(
684                             (PlayerApplicationSettings) msg.obj);
685                     notifyChanged(mAddressedPlayer.getPlaybackState());
686                     return true;
687 
688                 case MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED:
689                     processAvailablePlayerChanged();
690                     return true;
691 
692                 case MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM:
693                     mCoverArtPsm = msg.arg1;
694                     connectCoverArt();
695                     return true;
696 
697                 case MESSAGE_PROCESS_IMAGE_DOWNLOADED:
698                     AvrcpCoverArtManager.DownloadEvent event =
699                             (AvrcpCoverArtManager.DownloadEvent) msg.obj;
700                     String uuid = event.getUuid();
701                     Uri uri = event.getUri();
702                     logD("Received image for " + uuid + " at " + uri.toString());
703 
704                     // Let the addressed player know we got an image so it can see if the current
705                     // track now has cover artwork
706                     boolean addedArtwork = mAddressedPlayer.notifyImageDownload(uuid, uri);
707                     if (addedArtwork && isActive()) {
708                         BluetoothMediaBrowserService.trackChanged(
709                                 mAddressedPlayer.getCurrentTrack());
710                     }
711 
712                     // Let the browse tree know of the newly downloaded image so it can attach it to
713                     // all the items that need it. Notify of changed nodes accordingly
714                     Set<BrowseTree.BrowseNode> nodes = mBrowseTree.notifyImageDownload(uuid, uri);
715                     for (BrowseTree.BrowseNode node : nodes) {
716                         notifyChanged(node);
717                     }
718 
719                     // Delete images that were downloaded and entirely unused
720                     if (!addedArtwork && nodes.isEmpty()) {
721                         removeUnusedArtwork(uuid);
722                         removeUnusedArtworkFromBrowseTree();
723                     }
724 
725                     return true;
726 
727                 case DISCONNECT:
728                     transitionTo(mDisconnecting);
729                     return true;
730 
731                 default:
732                     return super.processMessage(msg);
733             }
734 
735         }
736 
processPlayItem(BrowseTree.BrowseNode node)737         private void processPlayItem(BrowseTree.BrowseNode node) {
738             if (node == null) {
739                 Log.w(TAG, "Invalid item to play");
740             } else {
741                 mService.playItemNative(
742                         mDeviceAddress, node.getScope(),
743                         node.getBluetoothID(), 0);
744             }
745         }
746 
passThru(int cmd)747         private synchronized void passThru(int cmd) {
748             logD("msgPassThru " + cmd);
749             // Some keys should be held until the next event.
750             if (mCurrentlyHeldKey != 0) {
751                 mService.sendPassThroughCommandNative(
752                         mDeviceAddress, mCurrentlyHeldKey,
753                         AvrcpControllerService.KEY_STATE_RELEASED);
754 
755                 if (mCurrentlyHeldKey == cmd) {
756                     // Return to prevent starting FF/FR operation again
757                     mCurrentlyHeldKey = 0;
758                     return;
759                 } else {
760                     // FF/FR is in progress and other operation is desired
761                     // so after stopping FF/FR, not returning so that command
762                     // can be sent for the desired operation.
763                     mCurrentlyHeldKey = 0;
764                 }
765             }
766 
767             // Send the pass through.
768             mService.sendPassThroughCommandNative(mDeviceAddress, cmd,
769                     AvrcpControllerService.KEY_STATE_PRESSED);
770 
771             if (isHoldableKey(cmd)) {
772                 // Release cmd next time a command is sent.
773                 mCurrentlyHeldKey = cmd;
774             } else {
775                 mService.sendPassThroughCommandNative(mDeviceAddress,
776                         cmd, AvrcpControllerService.KEY_STATE_RELEASED);
777             }
778         }
779 
isHoldableKey(int cmd)780         private boolean isHoldableKey(int cmd) {
781             return (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_REWIND)
782                     || (cmd == AvrcpControllerService.PASS_THRU_CMD_ID_FF);
783         }
784 
setRepeat(int repeatMode)785         private void setRepeat(int repeatMode) {
786             mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1,
787                     new byte[]{PlayerApplicationSettings.REPEAT_STATUS}, new byte[]{
788                             PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal(
789                                     PlayerApplicationSettings.REPEAT_STATUS, repeatMode)});
790         }
791 
setShuffle(int shuffleMode)792         private void setShuffle(int shuffleMode) {
793             mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1,
794                     new byte[]{PlayerApplicationSettings.SHUFFLE_STATUS}, new byte[]{
795                             PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal(
796                                     PlayerApplicationSettings.SHUFFLE_STATUS, shuffleMode)});
797         }
798 
processAvailablePlayerChanged()799         private void processAvailablePlayerChanged() {
800             logD("processAvailablePlayerChanged");
801             mBrowseTree.mRootNode.setCached(false);
802             mBrowseTree.mRootNode.setExpectedChildren(BrowseTree.DEFAULT_FOLDER_SIZE);
803             BluetoothMediaBrowserService.notifyChanged(mBrowseTree.mRootNode);
804             removeUnusedArtworkFromBrowseTree();
805             requestContents(mBrowseTree.mRootNode);
806         }
807     }
808 
809     // Handle the get folder listing action
810     // a) Fetch the listing of folders
811     // b) Once completed return the object listing
812     class GetFolderList extends State {
813         private static final String STATE_TAG = "Avrcp.GetFolderList";
814 
815         boolean mAbort;
816         BrowseTree.BrowseNode mBrowseNode;
817         BrowseTree.BrowseNode mNextStep;
818 
819         @Override
enter()820         public void enter() {
821             logD(STATE_TAG + ": Entering GetFolderList");
822             // Setup the timeouts.
823             sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
824             super.enter();
825             mAbort = false;
826             Message msg = getCurrentMessage();
827             if (msg.what == MESSAGE_GET_FOLDER_ITEMS) {
828                 mBrowseNode = (BrowseTree.BrowseNode) msg.obj;
829                 logD(STATE_TAG + ": new fetch request, node=" + mBrowseNode);
830             }
831 
832             if (mBrowseNode == null) {
833                 transitionTo(mConnected);
834             } else if (!mBrowsingConnected) {
835                 Log.w(TAG, "GetFolderItems: Browsing not connected, node=" + mBrowseNode);
836                 transitionTo(mConnected);
837             } else {
838                 int scope = mBrowseNode.getScope();
839                 if (scope == AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST
840                         || scope == AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING) {
841                     mBrowseNode.setExpectedChildren(BrowseTree.DEFAULT_FOLDER_SIZE);
842                 }
843                 mBrowseNode.setCached(false);
844                 navigateToFolderOrRetrieve(mBrowseNode);
845             }
846         }
847 
848         @Override
processMessage(Message msg)849         public boolean processMessage(Message msg) {
850             logD(STATE_TAG + " processMessage " + eventToString(msg.what));
851             switch (msg.what) {
852                 case MESSAGE_PROCESS_GET_FOLDER_ITEMS:
853                     ArrayList<AvrcpItem> folderList = (ArrayList<AvrcpItem>) msg.obj;
854                     int endIndicator = mBrowseNode.getExpectedChildren() - 1;
855                     logD("GetFolderItems: End " + endIndicator
856                             + " received " + folderList.size());
857 
858                     // Queue up image download if the item has an image and we don't have it yet
859                     // Only do this if the feature is enabled.
860                     for (AvrcpItem track : folderList) {
861                         if (shouldDownloadBrowsedImages()) {
862                             downloadImageIfNeeded(track);
863                         } else {
864                             track.setCoverArtUuid(null);
865                         }
866                     }
867 
868                     // Always update the node so that the user does not wait forever
869                     // for the list to populate.
870                     int newSize = mBrowseNode.addChildren(folderList);
871                     logD("Added " + newSize + " items to the browse tree");
872                     notifyChanged(mBrowseNode);
873 
874                     if (mBrowseNode.getChildrenCount() >= endIndicator || folderList.size() == 0
875                             || mAbort) {
876                         // If we have fetched all the elements or if the remotes sends us 0 elements
877                         // (which can lead us into a loop since mCurrInd does not proceed) we simply
878                         // abort.
879                         transitionTo(mConnected);
880                     } else {
881                         // Fetch the next set of items.
882                         fetchContents(mBrowseNode);
883                         // Reset the timeout message since we are doing a new fetch now.
884                         removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
885                         sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
886                     }
887                     break;
888                 case MESSAGE_PROCESS_SET_BROWSED_PLAYER:
889                     mBrowseTree.setCurrentBrowsedPlayer(mNextStep.getID(), msg.arg1, msg.arg2);
890                     removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
891                     sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
892                     navigateToFolderOrRetrieve(mBrowseNode);
893                     break;
894 
895                 case MESSAGE_PROCESS_FOLDER_PATH:
896                     mBrowseTree.setCurrentBrowsedFolder(mNextStep.getID());
897                     mBrowseTree.getCurrentBrowsedFolder().setExpectedChildren(msg.arg1);
898 
899                     // AVRCP Specification says, if we're not database aware, we must disconnect and
900                     // reconnect our BIP client each time we successfully change path
901                     refreshCoverArt();
902 
903                     if (mAbort) {
904                         transitionTo(mConnected);
905                     } else {
906                         removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
907                         sendMessageDelayed(MESSAGE_INTERNAL_CMD_TIMEOUT, CMD_TIMEOUT_MILLIS);
908                         navigateToFolderOrRetrieve(mBrowseNode);
909                     }
910                     break;
911 
912                 case MESSAGE_PROCESS_GET_PLAYER_ITEMS:
913                     logD("Received new available player items");
914                     BrowseTree.BrowseNode rootNode = mBrowseTree.mRootNode;
915 
916                     // The specification is not firm on what receiving available player changes
917                     // means relative to the existing player IDs, the addressed player and any
918                     // currently saved play status, track or now playing list metadata. We're going
919                     // to assume nothing and act verbosely, as some devices are known to reuse
920                     // Player IDs.
921                     if (!rootNode.isCached()) {
922                         List<AvrcpPlayer> playerList = (List<AvrcpPlayer>) msg.obj;
923 
924                         // Since players hold metadata, including cover art handles that point to
925                         // stored images, be sure to save image UUIDs so we can see if we can
926                         // remove them from storage after setting our new player object
927                         ArrayList<String> coverArtUuids = new ArrayList<String>();
928                         for (int i = 0; i < mAvailablePlayerList.size(); i++) {
929                             AvrcpPlayer player = mAvailablePlayerList.valueAt(i);
930                             AvrcpItem track = player.getCurrentTrack();
931                             if (track != null && track.getCoverArtUuid() != null) {
932                                 coverArtUuids.add(track.getCoverArtUuid());
933                             }
934                         }
935 
936                         mAvailablePlayerList.clear();
937                         for (AvrcpPlayer player : playerList) {
938                             mAvailablePlayerList.put(player.getId(), player);
939                         }
940 
941                         // If our new set of players contains our addressed player again then we
942                         // will replace it and re-download metadata. If not, we'll re-use the old
943                         // player to save the metadata queries.
944                         if (!mAvailablePlayerList.contains(mAddressedPlayerId)) {
945                             logD("Available player set doesn't contain the addressed player");
946                             mAvailablePlayerList.put(mAddressedPlayerId, mAddressedPlayer);
947                         } else {
948                             logD("Update addressed player with new available player metadata");
949                             mAddressedPlayer = mAvailablePlayerList.get(mAddressedPlayerId);
950                             mService.getCurrentMetadataNative(Utils.getByteAddress(mDevice));
951                             mService.getPlaybackStateNative(Utils.getByteAddress(mDevice));
952                             requestContents(mBrowseTree.mNowPlayingNode);
953                         }
954                         logD("AddressedPlayer = " + mAddressedPlayer);
955 
956                         // Check old cover art UUIDs for deletion
957                         for (String uuid : coverArtUuids) {
958                             removeUnusedArtwork(uuid);
959                         }
960 
961                         // Make sure our browse tree matches our received Available Player set only
962                         rootNode.addChildren(playerList);
963                         mBrowseTree.setCurrentBrowsedFolder(BrowseTree.ROOT);
964                         rootNode.setExpectedChildren(playerList.size());
965                         rootNode.setCached(true);
966                         notifyChanged(rootNode);
967                     }
968                     transitionTo(mConnected);
969                     break;
970 
971                 case MESSAGE_INTERNAL_CMD_TIMEOUT:
972                     // We have timed out to execute the request, we should simply send
973                     // whatever listing we have gotten until now.
974                     Log.w(TAG, "GetFolderItems: Timeout waiting for download, node=" + mBrowseNode);
975                     transitionTo(mConnected);
976                     break;
977 
978                 case MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE:
979                     // If we have gotten an error for OUT OF RANGE we have
980                     // already sent all the items to the client hence simply
981                     // transition to Connected state here.
982                     transitionTo(mConnected);
983                     break;
984 
985                 case MESSAGE_GET_FOLDER_ITEMS:
986                     BrowseTree.BrowseNode requested = (BrowseTree.BrowseNode) msg.obj;
987                     if (!mBrowseNode.equals(requested) || requested.isNowPlaying()) {
988                         if (shouldAbort(mBrowseNode.getScope(), requested.getScope())) {
989                             mAbort = true;
990                         }
991                         deferMessage(msg);
992                         logD("GetFolderItems: Enqueue new request for node=" + requested
993                                 + ", abort=" + mAbort);
994                     } else {
995                         logD("GetFolderItems: Ignore request, node=" + requested);
996                     }
997                     break;
998 
999                 default:
1000                     // All of these messages should be handled by parent state immediately.
1001                     logD("GetFolderItems: Passing message to parent state, type="
1002                             + eventToString(msg.what));
1003                     return false;
1004             }
1005             return true;
1006         }
1007 
1008         /**
1009          * shouldAbort calculates the cases where fetching the current directory is no longer
1010          * necessary.
1011          *
1012          * @return true:  a new folder in the same scope
1013          * a new player while fetching contents of a folder
1014          * false: other cases, specifically Now Playing while fetching a folder
1015          */
shouldAbort(int currentScope, int fetchScope)1016         private boolean shouldAbort(int currentScope, int fetchScope) {
1017             if ((currentScope == fetchScope)
1018                     || (currentScope == AvrcpControllerService.BROWSE_SCOPE_VFS
1019                     && fetchScope == AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST)) {
1020                 return true;
1021             }
1022             return false;
1023         }
1024 
fetchContents(BrowseTree.BrowseNode target)1025         private void fetchContents(BrowseTree.BrowseNode target) {
1026             int start = target.getChildrenCount();
1027             int end = Math.min(target.getExpectedChildren(), target.getChildrenCount()
1028                     + ITEM_PAGE_SIZE) - 1;
1029             logD("fetchContents(title=" + target.getID() + ", scope=" + target.getScope()
1030                     + ", start=" + start + ", end=" + end + ", expected="
1031                     + target.getExpectedChildren() + ")");
1032             switch (target.getScope()) {
1033                 case AvrcpControllerService.BROWSE_SCOPE_PLAYER_LIST:
1034                     mService.getPlayerListNative(mDeviceAddress,
1035                             start, end);
1036                     break;
1037                 case AvrcpControllerService.BROWSE_SCOPE_NOW_PLAYING:
1038                     mService.getNowPlayingListNative(
1039                             mDeviceAddress, start, end);
1040                     break;
1041                 case AvrcpControllerService.BROWSE_SCOPE_VFS:
1042                     mService.getFolderListNative(mDeviceAddress,
1043                             start, end);
1044                     break;
1045                 default:
1046                     Log.e(TAG, STATE_TAG + " Scope " + target.getScope()
1047                             + " cannot be handled here.");
1048             }
1049         }
1050 
1051         /* One of several things can happen when trying to get a folder list
1052          *
1053          *
1054          * 0: The folder handle is no longer valid
1055          * 1: The folder contents can be retrieved directly (NowPlaying, Root, Current)
1056          * 2: The folder is a browsable player
1057          * 3: The folder is a non browsable player
1058          * 4: The folder is not a child of the current folder
1059          * 5: The folder is a child of the current folder
1060          *
1061          */
navigateToFolderOrRetrieve(BrowseTree.BrowseNode target)1062         private void navigateToFolderOrRetrieve(BrowseTree.BrowseNode target) {
1063             mNextStep = mBrowseTree.getNextStepToFolder(target);
1064             logD("NAVIGATING From "
1065                     + mBrowseTree.getCurrentBrowsedFolder().toString());
1066             logD("NAVIGATING Toward " + target.toString());
1067             if (mNextStep == null) {
1068                 return;
1069             } else if (target.equals(mBrowseTree.mNowPlayingNode)
1070                     || target.equals(mBrowseTree.mRootNode)
1071                     || mNextStep.equals(mBrowseTree.getCurrentBrowsedFolder())) {
1072                 fetchContents(mNextStep);
1073             } else if (mNextStep.isPlayer()) {
1074                 logD("NAVIGATING Player " + mNextStep.toString());
1075                 if (mNextStep.isBrowsable()) {
1076                     mService.setBrowsedPlayerNative(
1077                             mDeviceAddress, (int) mNextStep.getBluetoothID());
1078                 } else {
1079                     logD("Player doesn't support browsing");
1080                     mNextStep.setCached(true);
1081                     transitionTo(mConnected);
1082                 }
1083             } else if (mNextStep.equals(mBrowseTree.mNavigateUpNode)) {
1084                 logD("NAVIGATING UP " + mNextStep.toString());
1085                 mNextStep = mBrowseTree.getCurrentBrowsedFolder().getParent();
1086                 mBrowseTree.getCurrentBrowsedFolder().setCached(false);
1087                 removeUnusedArtworkFromBrowseTree();
1088                 mService.changeFolderPathNative(
1089                         mDeviceAddress,
1090                         AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_UP,
1091                         0);
1092 
1093             } else {
1094                 logD("NAVIGATING DOWN " + mNextStep.toString());
1095                 mService.changeFolderPathNative(
1096                         mDeviceAddress,
1097                         AvrcpControllerService.FOLDER_NAVIGATION_DIRECTION_DOWN,
1098                         mNextStep.getBluetoothID());
1099             }
1100         }
1101 
1102         @Override
exit()1103         public void exit() {
1104             logd("GetFolderItems: fetch complete, node=" + mBrowseNode);
1105             removeMessages(MESSAGE_INTERNAL_CMD_TIMEOUT);
1106 
1107             // Whatever we have, notify on it so the UI doesn't hang
1108             if (mBrowseNode != null) {
1109                 mBrowseNode.setCached(true);
1110                 notifyChanged(mBrowseNode);
1111             }
1112 
1113             mBrowseNode = null;
1114             super.exit();
1115         }
1116     }
1117 
1118     protected class Disconnecting extends State {
1119         @Override
enter()1120         public void enter() {
1121             disconnectCoverArt();
1122             onBrowsingDisconnected();
1123             if (mService.sBrowseTree != null) {
1124                 mService.sBrowseTree.mRootNode.removeChild(mBrowseTree.mRootNode);
1125                 BluetoothMediaBrowserService.notifyChanged(mService.sBrowseTree.mRootNode);
1126             }
1127             broadcastConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING);
1128             transitionTo(mDisconnected);
1129         }
1130     }
1131 
1132     /**
1133      * Handle a request to align our local volume with the volume of a remote device. If
1134      * we're assuming the source volume is fixed then a response of ABS_VOL_MAX will always be
1135      * sent and no volume adjustment action will be taken on the sink side.
1136      *
1137      * @param absVol A volume level based on a domain of [0, ABS_VOL_MAX]
1138      * @param label Volume notification label
1139      */
handleAbsVolumeRequest(int absVol, int label)1140     private void handleAbsVolumeRequest(int absVol, int label) {
1141         logD("handleAbsVolumeRequest: absVol = " + absVol + ", label = " + label);
1142         if (mIsVolumeFixed) {
1143             logD("Source volume is assumed to be fixed, responding with max volume");
1144             absVol = ABS_VOL_BASE;
1145         } else {
1146             mVolumeChangedNotificationsToIgnore++;
1147             removeMessages(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT);
1148             sendMessageDelayed(MESSAGE_INTERNAL_ABS_VOL_TIMEOUT,
1149                     ABS_VOL_TIMEOUT_MILLIS);
1150             setAbsVolume(absVol);
1151         }
1152         mService.sendAbsVolRspNative(mDeviceAddress, absVol, label);
1153     }
1154 
1155     /**
1156      * Align our volume with a requested absolute volume level
1157      *
1158      * @param absVol A volume level based on a domain of [0, ABS_VOL_MAX]
1159      */
setAbsVolume(int absVol)1160     private void setAbsVolume(int absVol) {
1161         int maxLocalVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
1162         int curLocalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
1163         int reqLocalVolume = (maxLocalVolume * absVol) / ABS_VOL_BASE;
1164         logD("setAbsVolme: absVol = " + absVol + ", reqLocal = " + reqLocalVolume
1165                 + ", curLocal = " + curLocalVolume + ", maxLocal = " + maxLocalVolume);
1166 
1167         /*
1168          * In some cases change in percentage is not sufficient enough to warrant
1169          * change in index values which are in range of 0-15. For such cases
1170          * no action is required
1171          */
1172         if (reqLocalVolume != curLocalVolume) {
1173             mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, reqLocalVolume,
1174                     AudioManager.FLAG_SHOW_UI);
1175         }
1176     }
1177 
getAbsVolume()1178     private int getAbsVolume() {
1179         if (mIsVolumeFixed) {
1180             return ABS_VOL_BASE;
1181         }
1182         int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
1183         int currIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
1184         int newIndex = (currIndex * ABS_VOL_BASE) / maxVolume;
1185         return newIndex;
1186     }
1187 
shouldDownloadBrowsedImages()1188     private boolean shouldDownloadBrowsedImages() {
1189         return mService.getResources()
1190                 .getBoolean(R.bool.avrcp_controller_cover_art_browsed_images);
1191     }
1192 
downloadImageIfNeeded(AvrcpItem track)1193     private void downloadImageIfNeeded(AvrcpItem track) {
1194         if (mCoverArtManager == null) return;
1195         String uuid = track.getCoverArtUuid();
1196         Uri imageUri = null;
1197         if (uuid != null) {
1198             imageUri = mCoverArtManager.getImageUri(mDevice, uuid);
1199             if (imageUri != null) {
1200                 track.setCoverArtLocation(imageUri);
1201             } else {
1202                 mCoverArtManager.downloadImage(mDevice, uuid);
1203             }
1204         }
1205     }
1206 
getFocusState()1207     private int getFocusState() {
1208         int focusState = AudioManager.ERROR;
1209         A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
1210         if (a2dpSinkService != null) {
1211             focusState = a2dpSinkService.getFocusState();
1212         }
1213         return focusState;
1214     }
1215 
1216     MediaSessionCompat.Callback mSessionCallbacks = new MediaSessionCompat.Callback() {
1217         @Override
1218         public void onPlay() {
1219             logD("onPlay");
1220             onPrepare();
1221             sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PLAY);
1222         }
1223 
1224         @Override
1225         public void onPause() {
1226             logD("onPause");
1227             // If we receive a local pause/stop request and send it out then we need to signal that
1228             // the intent is to stay paused if we recover focus from a transient loss
1229             if (getFocusState() == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
1230                 logD("Received a pause while in a transient loss. Do not recover anymore.");
1231                 mShouldSendPlayOnFocusRecovery = false;
1232             }
1233             sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
1234         }
1235 
1236         @Override
1237         public void onSkipToNext() {
1238             logD("onSkipToNext");
1239             onPrepare();
1240             sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FORWARD);
1241         }
1242 
1243         @Override
1244         public void onSkipToPrevious() {
1245             logD("onSkipToPrevious");
1246             onPrepare();
1247             sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_BACKWARD);
1248         }
1249 
1250         @Override
1251         public void onSkipToQueueItem(long id) {
1252             logD("onSkipToQueueItem id=" + id);
1253             onPrepare();
1254             BrowseTree.BrowseNode node = mBrowseTree.getTrackFromNowPlayingList((int) id);
1255             if (node != null) {
1256                 sendMessage(MESSAGE_PLAY_ITEM, node);
1257             }
1258         }
1259 
1260         @Override
1261         public void onStop() {
1262             logD("onStop");
1263             // If we receive a local pause/stop request and send it out then we need to signal that
1264             // the intent is to stay paused if we recover focus from a transient loss
1265             if (getFocusState() == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
1266                 logD("Received a stop while in a transient loss. Do not recover anymore.");
1267                 mShouldSendPlayOnFocusRecovery = false;
1268             }
1269             sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_STOP);
1270         }
1271 
1272         @Override
1273         public void onPrepare() {
1274             logD("onPrepare");
1275             A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
1276             if (a2dpSinkService != null) {
1277                 a2dpSinkService.requestAudioFocus(mDevice, true);
1278             }
1279         }
1280 
1281         @Override
1282         public void onRewind() {
1283             logD("onRewind");
1284             sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_REWIND);
1285         }
1286 
1287         @Override
1288         public void onFastForward() {
1289             logD("onFastForward");
1290             sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_FF);
1291         }
1292 
1293         @Override
1294         public void onPlayFromMediaId(String mediaId, Bundle extras) {
1295             logD("onPlayFromMediaId");
1296             // Play the item if possible.
1297             onPrepare();
1298             BrowseTree.BrowseNode node = mBrowseTree.findBrowseNodeByID(mediaId);
1299             if (node != null) {
1300                 // node was found on this bluetooth device
1301                 sendMessage(MESSAGE_PLAY_ITEM, node);
1302             } else {
1303                 // node was not found on this device, pause here, and play on another device
1304                 sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);
1305                 mService.playItem(mediaId);
1306             }
1307         }
1308 
1309         @Override
1310         public void onSetRepeatMode(int repeatMode) {
1311             logD("onSetRepeatMode");
1312             sendMessage(MSG_AVRCP_SET_REPEAT, repeatMode);
1313         }
1314 
1315         @Override
1316         public void onSetShuffleMode(int shuffleMode) {
1317             logD("onSetShuffleMode");
1318             sendMessage(MSG_AVRCP_SET_SHUFFLE, shuffleMode);
1319 
1320         }
1321     };
1322 
broadcastConnectionStateChanged(int currentState)1323     protected void broadcastConnectionStateChanged(int currentState) {
1324         if (mMostRecentState == currentState) {
1325             return;
1326         }
1327         if (currentState == BluetoothProfile.STATE_CONNECTED) {
1328             MetricsLogger.logProfileConnectionEvent(
1329                     BluetoothMetricsProto.ProfileId.AVRCP_CONTROLLER);
1330         }
1331         logD("Connection state " + mDevice + ": " + mMostRecentState + "->" + currentState);
1332         Intent intent = new Intent(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
1333         intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, mMostRecentState);
1334         intent.putExtra(BluetoothProfile.EXTRA_STATE, currentState);
1335         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
1336         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
1337         mMostRecentState = currentState;
1338         Utils.sendBroadcast(mService, intent, BLUETOOTH_CONNECT,
1339                 Utils.getTempAllowlistBroadcastOptions());
1340     }
1341 
shouldRequestFocus()1342     private boolean shouldRequestFocus() {
1343         return mService.getResources()
1344                 .getBoolean(R.bool.a2dp_sink_automatically_request_audio_focus);
1345     }
1346 
eventToString(int event)1347     private static String eventToString(int event) {
1348         switch (event) {
1349             case CONNECT:
1350                 return "CONNECT";
1351             case DISCONNECT:
1352                 return "DISCONNECT";
1353             case ACTIVE_DEVICE_CHANGE:
1354                 return "ACTIVE_DEVICE_CHANGE";
1355             case AUDIO_FOCUS_STATE_CHANGE:
1356                 return "AUDIO_FOCUS_STATE_CHANGE";
1357             case CLEANUP:
1358                 return "CLEANUP";
1359             case CONNECT_TIMEOUT:
1360                 return "CONNECT_TIMEOUT";
1361             case MESSAGE_INTERNAL_ABS_VOL_TIMEOUT:
1362                 return "MESSAGE_INTERNAL_ABS_VOL_TIMEOUT";
1363             case STACK_EVENT:
1364                 return "STACK_EVENT";
1365             case MESSAGE_INTERNAL_CMD_TIMEOUT:
1366                 return "MESSAGE_INTERNAL_CMD_TIMEOUT";
1367             case MESSAGE_PROCESS_SET_ABS_VOL_CMD:
1368                 return "MESSAGE_PROCESS_SET_ABS_VOL_CMD";
1369             case MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION:
1370                 return "MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION";
1371             case MESSAGE_PROCESS_TRACK_CHANGED:
1372                 return "MESSAGE_PROCESS_TRACK_CHANGED";
1373             case MESSAGE_PROCESS_PLAY_POS_CHANGED:
1374                 return "MESSAGE_PROCESS_PLAY_POS_CHANGED";
1375             case MESSAGE_PROCESS_PLAY_STATUS_CHANGED:
1376                 return "MESSAGE_PROCESS_PLAY_STATUS_CHANGED";
1377             case MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION:
1378                 return "MESSAGE_PROCESS_VOLUME_CHANGED_NOTIFICATION";
1379             case MESSAGE_PROCESS_GET_FOLDER_ITEMS:
1380                 return "MESSAGE_PROCESS_GET_FOLDER_ITEMS";
1381             case MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE:
1382                 return "MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE";
1383             case MESSAGE_PROCESS_GET_PLAYER_ITEMS:
1384                 return "MESSAGE_PROCESS_GET_PLAYER_ITEMS";
1385             case MESSAGE_PROCESS_FOLDER_PATH:
1386                 return "MESSAGE_PROCESS_FOLDER_PATH";
1387             case MESSAGE_PROCESS_SET_BROWSED_PLAYER:
1388                 return "MESSAGE_PROCESS_SET_BROWSED_PLAYER";
1389             case MESSAGE_PROCESS_SET_ADDRESSED_PLAYER:
1390                 return "MESSAGE_PROCESS_SET_ADDRESSED_PLAYER";
1391             case MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED:
1392                 return "MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED";
1393             case MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED:
1394                 return "MESSAGE_PROCESS_NOW_PLAYING_CONTENTS_CHANGED";
1395             case MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS:
1396                 return "MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS";
1397             case MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS:
1398                 return "MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS";
1399             case MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED:
1400                 return "MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED";
1401             case MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM:
1402                 return "MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM";
1403             case MESSAGE_GET_FOLDER_ITEMS:
1404                 return "MESSAGE_GET_FOLDER_ITEMS";
1405             case MESSAGE_PLAY_ITEM:
1406                 return "MESSAGE_PLAY_ITEM";
1407             case MSG_AVRCP_PASSTHRU:
1408                 return "MSG_AVRCP_PASSTHRU";
1409             case MSG_AVRCP_SET_SHUFFLE:
1410                 return "MSG_AVRCP_SET_SHUFFLE";
1411             case MSG_AVRCP_SET_REPEAT:
1412                 return "MSG_AVRCP_SET_REPEAT";
1413             case MESSAGE_PROCESS_IMAGE_DOWNLOADED:
1414                 return "MESSAGE_PROCESS_IMAGE_DOWNLOADED";
1415             default:
1416                 return "UNKNOWN_EVENT_ID_" + event;
1417         }
1418     }
1419 }
1420