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