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