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