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