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