• 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.bluetooth.BluetoothProfile.STATE_CONNECTED;
20 import static android.bluetooth.BluetoothProfile.STATE_CONNECTING;
21 import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED;
22 
23 import static java.util.Objects.requireNonNull;
24 
25 import android.bluetooth.BluetoothAdapter;
26 import android.bluetooth.BluetoothDevice;
27 import android.content.Intent;
28 import android.media.AudioManager;
29 import android.support.v4.media.MediaBrowserCompat.MediaItem;
30 import android.sysprop.BluetoothProperties;
31 import android.util.Log;
32 
33 import com.android.bluetooth.BluetoothPrefs;
34 import com.android.bluetooth.R;
35 import com.android.bluetooth.Utils;
36 import com.android.bluetooth.a2dpsink.A2dpSinkService;
37 import com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService.BrowseResult;
38 import com.android.bluetooth.btservice.AdapterService;
39 import com.android.bluetooth.btservice.ProfileService;
40 import com.android.internal.annotations.VisibleForTesting;
41 
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.UUID;
47 import java.util.concurrent.ConcurrentHashMap;
48 
49 /** Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application. */
50 public class AvrcpControllerService extends ProfileService {
51     private static final String TAG = AvrcpControllerService.class.getSimpleName();
52 
53     static final int MAXIMUM_CONNECTED_DEVICES = 5;
54 
55     /** Owned Components */
56     private static final String ON_ERROR_SETTINGS_ACTIVITY =
57             BluetoothPrefs.class.getCanonicalName();
58 
59     private static final String COVER_ART_PROVIDER = AvrcpCoverArtProvider.class.getCanonicalName();
60 
61     /* Folder/Media Item scopes.
62      * Keep in sync with AVRCP 1.6 sec. 6.10.1
63      */
64     public static final byte BROWSE_SCOPE_PLAYER_LIST = 0x00;
65     public static final byte BROWSE_SCOPE_VFS = 0x01;
66     public static final byte BROWSE_SCOPE_SEARCH = 0x02;
67     public static final byte BROWSE_SCOPE_NOW_PLAYING = 0x03;
68 
69     /* Folder navigation directions
70      * This is borrowed from AVRCP 1.6 spec and must be kept with same values
71      */
72     public static final byte FOLDER_NAVIGATION_DIRECTION_UP = 0x00;
73     public static final byte FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01;
74 
75     /*
76      * KeyCoded for Pass Through Commands
77      */
78     public static final int PASS_THRU_CMD_ID_PLAY = 0x44;
79     public static final int PASS_THRU_CMD_ID_PAUSE = 0x46;
80     public static final int PASS_THRU_CMD_ID_VOL_UP = 0x41;
81     public static final int PASS_THRU_CMD_ID_VOL_DOWN = 0x42;
82     public static final int PASS_THRU_CMD_ID_STOP = 0x45;
83     public static final int PASS_THRU_CMD_ID_FF = 0x49;
84     public static final int PASS_THRU_CMD_ID_REWIND = 0x48;
85     public static final int PASS_THRU_CMD_ID_FORWARD = 0x4B;
86     public static final int PASS_THRU_CMD_ID_BACKWARD = 0x4C;
87 
88     /* Key State Variables */
89     public static final int KEY_STATE_PRESSED = 0;
90     public static final int KEY_STATE_RELEASED = 1;
91 
92     /* Active Device State Variables */
93     public static final int DEVICE_STATE_INACTIVE = 0;
94     public static final int DEVICE_STATE_ACTIVE = 1;
95 
96     private static AvrcpControllerService sService;
97 
98     private final Object mActiveDeviceLock = new Object();
99 
100     private final AdapterService mAdapterService;
101     private final AvrcpControllerNativeInterface mNativeInterface;
102     private final AvrcpCoverArtManager mCoverArtManager;
103     private final boolean mCoverArtEnabled;
104 
105     private final BrowseTree mBrowseTree;
106 
107     @VisibleForTesting
108     final Map<BluetoothDevice, AvrcpControllerStateMachine> mDeviceStateMap =
109             new ConcurrentHashMap<>();
110 
111     private BluetoothDevice mActiveDevice = null;
112 
113     private class ImageDownloadCallback implements AvrcpCoverArtManager.Callback {
114         @Override
onImageDownloadComplete( BluetoothDevice device, AvrcpCoverArtManager.DownloadEvent event)115         public void onImageDownloadComplete(
116                 BluetoothDevice device, AvrcpCoverArtManager.DownloadEvent event) {
117             Log.d(
118                     TAG,
119                     "Image downloaded [device: "
120                             + device
121                             + ", uuid: "
122                             + event.uuid()
123                             + ", uri: "
124                             + event.uri());
125             AvrcpControllerStateMachine stateMachine = getStateMachine(device);
126             if (stateMachine == null) {
127                 Log.e(TAG, "No state machine found for device " + device);
128                 mCoverArtManager.removeImage(device, event.uuid());
129                 return;
130             }
131             stateMachine.sendMessage(
132                     AvrcpControllerStateMachine.MESSAGE_PROCESS_IMAGE_DOWNLOADED, event);
133         }
134     }
135 
AvrcpControllerService(AdapterService adapterService)136     public AvrcpControllerService(AdapterService adapterService) {
137         this(adapterService, AvrcpControllerNativeInterface.getInstance());
138     }
139 
140     @VisibleForTesting
AvrcpControllerService( AdapterService adapterService, AvrcpControllerNativeInterface nativeInterface)141     public AvrcpControllerService(
142             AdapterService adapterService, AvrcpControllerNativeInterface nativeInterface) {
143         super(requireNonNull(adapterService));
144         mAdapterService = adapterService;
145         mNativeInterface = requireNonNull(nativeInterface);
146         mNativeInterface.init(this);
147 
148         setComponentAvailable(ON_ERROR_SETTINGS_ACTIVITY, true);
149         mCoverArtEnabled = getResources().getBoolean(R.bool.avrcp_controller_enable_cover_art);
150         if (mCoverArtEnabled) {
151             setComponentAvailable(COVER_ART_PROVIDER, true);
152             mCoverArtManager = new AvrcpCoverArtManager(this, new ImageDownloadCallback());
153         } else {
154             mCoverArtManager = null;
155         }
156 
157         mBrowseTree = new BrowseTree(null);
158         setAvrcpControllerService(this);
159 
160         // Start the media browser service.
161         Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class);
162         startService(startIntent);
163     }
164 
isEnabled()165     public static boolean isEnabled() {
166         return BluetoothProperties.isProfileAvrcpControllerEnabled().orElse(false);
167     }
168 
169     @Override
cleanup()170     public synchronized void cleanup() {
171         Log.i(TAG, "Cleanup AVRCP Controller Service");
172 
173         setActiveDevice(null);
174         Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class);
175         stopService(stopIntent);
176         for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
177             stateMachine.quitNow();
178         }
179         mDeviceStateMap.clear();
180 
181         setAvrcpControllerService(null);
182         if (mCoverArtManager != null) {
183             mCoverArtManager.cleanup();
184             setComponentAvailable(COVER_ART_PROVIDER, false);
185         }
186         setComponentAvailable(ON_ERROR_SETTINGS_ACTIVITY, false);
187         mNativeInterface.cleanup();
188     }
189 
getBrowseTree()190     BrowseTree getBrowseTree() {
191         return mBrowseTree;
192     }
193 
getAvrcpControllerService()194     public static synchronized AvrcpControllerService getAvrcpControllerService() {
195         return sService;
196     }
197 
198     /** Testing API to inject a mock AvrcpControllerService */
199     @VisibleForTesting
setAvrcpControllerService(AvrcpControllerService service)200     public static synchronized void setAvrcpControllerService(AvrcpControllerService service) {
201         sService = service;
202     }
203 
204     /** Get the current active device */
getActiveDevice()205     public BluetoothDevice getActiveDevice() {
206         synchronized (mActiveDeviceLock) {
207             return mActiveDevice;
208         }
209     }
210 
211     /** Set the current active device, notify devices of activity status */
212     @VisibleForTesting
setActiveDevice(BluetoothDevice device)213     boolean setActiveDevice(BluetoothDevice device) {
214         Log.d(TAG, "setActiveDevice(device=" + device + ")");
215         A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
216         if (a2dpSinkService == null) {
217             Log.w(TAG, "setActiveDevice(device=" + device + "): A2DP Sink not available");
218             return false;
219         }
220 
221         BluetoothDevice currentActiveDevice = getActiveDevice();
222         if ((device == null && currentActiveDevice == null)
223                 || (device != null && device.equals(currentActiveDevice))) {
224             return true;
225         }
226 
227         // Try and update the active device
228         synchronized (mActiveDeviceLock) {
229             if (a2dpSinkService.setActiveDevice(device)) {
230                 mActiveDevice = device;
231 
232                 // Pause the old active device
233                 if (currentActiveDevice != null) {
234                     AvrcpControllerStateMachine oldStateMachine =
235                             getStateMachine(currentActiveDevice);
236                     if (oldStateMachine != null) {
237                         oldStateMachine.setDeviceState(DEVICE_STATE_INACTIVE);
238                     }
239                 }
240 
241                 AvrcpControllerStateMachine stateMachine = getStateMachine(device);
242                 if (stateMachine != null) {
243                     stateMachine.setDeviceState(DEVICE_STATE_ACTIVE);
244                 } else {
245                     BluetoothMediaBrowserService.reset();
246                 }
247                 return true;
248             }
249         }
250 
251         Log.w(TAG, "setActiveDevice(device=" + device + "): A2DP Sink request failed");
252         return false;
253     }
254 
getCurrentMetadataIfNoCoverArt(BluetoothDevice device)255     protected void getCurrentMetadataIfNoCoverArt(BluetoothDevice device) {
256         if (device == null) return;
257         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
258         if (stateMachine == null) return;
259         AvrcpItem track = stateMachine.getCurrentTrack();
260         if (track != null && track.getCoverArtLocation() == null) {
261             mNativeInterface.getCurrentMetadata(Utils.getByteAddress(device));
262         }
263     }
264 
265     @VisibleForTesting
refreshContents(BrowseTree.BrowseNode node)266     void refreshContents(BrowseTree.BrowseNode node) {
267         BluetoothDevice device = node.getDevice();
268         if (device == null) {
269             return;
270         }
271         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
272         if (stateMachine != null) {
273             stateMachine.requestContents(node);
274         }
275     }
276 
playItem(String parentMediaId)277     void playItem(String parentMediaId) {
278         Log.d(TAG, "playItem(" + parentMediaId + ")");
279         // Check if the requestedNode is a player rather than a song
280         BrowseTree.BrowseNode requestedNode = mBrowseTree.findBrowseNodeByID(parentMediaId);
281         if (requestedNode == null) {
282             for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
283                 // Check each state machine for the song and then play it
284                 requestedNode = stateMachine.findNode(parentMediaId);
285                 if (requestedNode != null) {
286                     Log.d(TAG, "Found a node, node=" + requestedNode);
287                     BluetoothDevice device = stateMachine.getDevice();
288                     if (device != null) {
289                         setActiveDevice(device);
290                     }
291                     stateMachine.playItem(requestedNode);
292                     break;
293                 }
294             }
295         }
296     }
297 
298     /*Java API*/
299 
300     /**
301      * Get a List of MediaItems that are children of the specified media Id
302      *
303      * @param parentMediaId The player or folder to get the contents of
304      * @return List of Children if available, an empty list if there are none, or null if a search
305      *     must be performed.
306      */
getContents(String parentMediaId)307     public synchronized BrowseResult getContents(String parentMediaId) {
308         Log.d(TAG, "getContents(" + parentMediaId + ")");
309 
310         BrowseTree.BrowseNode requestedNode = mBrowseTree.findBrowseNodeByID(parentMediaId);
311         if (requestedNode == null) {
312             for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
313                 requestedNode = stateMachine.findNode(parentMediaId);
314                 if (requestedNode != null) {
315                     break;
316                 }
317             }
318         }
319 
320         // If we don't find a node in the tree then do not have any way to browse for the contents.
321         // Return an empty list instead.
322         if (requestedNode == null) {
323             Log.e(TAG, "getContents(" + parentMediaId + "): Failed to find node");
324             return new BrowseResult(new ArrayList(0), BrowseResult.ERROR_MEDIA_ID_INVALID);
325         }
326         Log.d(
327                 TAG,
328                 ("getContents(" + parentMediaId + "): ")
329                         + ("node=" + requestedNode)
330                         + (", device=" + requestedNode.getDevice()));
331         if (parentMediaId.equals(BrowseTree.ROOT) && requestedNode.getChildrenCount() == 0) {
332             return new BrowseResult(null, BrowseResult.NO_DEVICE_CONNECTED);
333         }
334         // If we found a node and it belongs to a device then go ahead and make it active
335         BluetoothDevice device = requestedNode.getDevice();
336         if (device != null) {
337             setActiveDevice(device);
338         }
339 
340         List<MediaItem> contents = requestedNode.getContents();
341 
342         if (!requestedNode.isCached()) {
343             Log.d(TAG, "getContents(" + parentMediaId + "): node download pending");
344             refreshContents(requestedNode);
345             /* Ongoing downloads can have partial results and we want to make sure they get sent
346              * to the client. If a download gets kicked off as a result of this request, the
347              * contents will be null until the first results arrive.
348              */
349             return new BrowseResult(contents, BrowseResult.DOWNLOAD_PENDING);
350         }
351         Log.d(
352                 TAG,
353                 "getContents("
354                         + parentMediaId
355                         + "): return node, contents="
356                         + requestedNode.getContents());
357         return new BrowseResult(contents, BrowseResult.SUCCESS);
358     }
359 
360     @Override
initBinder()361     protected IProfileServiceBinder initBinder() {
362         return new AvrcpControllerServiceBinder(this);
363     }
364 
365     // Called by JNI when a device has connected or disconnected.
366     @VisibleForTesting
onConnectionStateChanged( boolean remoteControlConnected, boolean browsingConnected, BluetoothDevice device)367     synchronized void onConnectionStateChanged(
368             boolean remoteControlConnected, boolean browsingConnected, BluetoothDevice device) {
369         StackEvent event =
370                 StackEvent.connectionStateChanged(remoteControlConnected, browsingConnected);
371         AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device);
372         if (remoteControlConnected || browsingConnected) {
373             stateMachine.connect(event);
374             // The first device to connect gets to be the active device
375             if (getActiveDevice() == null) {
376                 setActiveDevice(device);
377             }
378         } else {
379             stateMachine.disconnect();
380             if (device.equals(getActiveDevice())) {
381                 setActiveDevice(null);
382             }
383         }
384     }
385 
386     // Called by JNI to notify Avrcp of a remote device's Cover Art PSM
387     @VisibleForTesting
getRcPsm(BluetoothDevice device, int psm)388     void getRcPsm(BluetoothDevice device, int psm) {
389         AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device);
390         stateMachine.sendMessage(
391                 AvrcpControllerStateMachine.MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM, psm);
392     }
393 
394     // Called by JNI when remote wants to receive absolute volume notifications.
395     @VisibleForTesting
handleRegisterNotificationAbsVol(BluetoothDevice device, byte label)396     synchronized void handleRegisterNotificationAbsVol(BluetoothDevice device, byte label) {
397         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
398         if (stateMachine != null) {
399             stateMachine.sendMessage(
400                     AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION,
401                     label);
402         }
403     }
404 
405     // Called by JNI when remote wants to set absolute volume.
406     @VisibleForTesting
handleSetAbsVolume(BluetoothDevice device, byte absVol, byte label)407     synchronized void handleSetAbsVolume(BluetoothDevice device, byte absVol, byte label) {
408         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
409         if (stateMachine != null) {
410             stateMachine.sendMessage(
411                     AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
412         }
413     }
414 
415     /**
416      * Notify AVRCP Controller of an audio focus state change so we can make requests of the active
417      * player to stop and start playing.
418      */
onAudioFocusStateChanged(int state)419     public void onAudioFocusStateChanged(int state) {
420         Log.d(TAG, "onAudioFocusStateChanged(state=" + state + ")");
421 
422         // Make sure the active device isn't changed while we're processing the event so play/pause
423         // commands get routed to the correct device
424         synchronized (mActiveDeviceLock) {
425             switch (state) {
426                 case AudioManager.AUDIOFOCUS_GAIN:
427                     BluetoothMediaBrowserService.setActive(true);
428                     break;
429                 case AudioManager.AUDIOFOCUS_LOSS:
430                     BluetoothMediaBrowserService.setActive(false);
431                     break;
432             }
433             BluetoothDevice device = getActiveDevice();
434             if (device == null) {
435                 Log.w(TAG, "No active device set, ignore focus change");
436                 return;
437             }
438 
439             AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
440             if (stateMachine == null) {
441                 Log.w(TAG, "No state machine for active device.");
442                 return;
443             }
444             stateMachine.sendMessage(AvrcpControllerStateMachine.AUDIO_FOCUS_STATE_CHANGE, state);
445         }
446     }
447 
448     // Called by JNI when a track changes and local AvrcpController is registered for updates.
449     @VisibleForTesting
onTrackChanged( BluetoothDevice device, byte numAttributes, int[] attributes, String[] attribVals)450     synchronized void onTrackChanged(
451             BluetoothDevice device, byte numAttributes, int[] attributes, String[] attribVals) {
452         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
453         if (stateMachine != null) {
454             AvrcpItem.Builder aib = new AvrcpItem.Builder();
455             aib.fromAvrcpAttributeArray(attributes, attribVals);
456             aib.setDevice(device);
457             aib.setItemType(AvrcpItem.TYPE_MEDIA);
458             aib.setUuid(UUID.randomUUID().toString());
459             AvrcpItem item = aib.build();
460             if (mCoverArtManager != null) {
461                 String handle = item.getCoverArtHandle();
462                 if (handle != null) {
463                     item.setCoverArtUuid(mCoverArtManager.getUuidForHandle(device, handle));
464                 }
465             }
466             stateMachine.sendMessage(
467                     AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED, item);
468         }
469     }
470 
471     // Called by JNI periodically based upon timer to update play position
472     @VisibleForTesting
onPlayPositionChanged( BluetoothDevice device, int songLen, int currSongPosition)473     synchronized void onPlayPositionChanged(
474             BluetoothDevice device, int songLen, int currSongPosition) {
475         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
476         if (stateMachine != null) {
477             stateMachine.sendMessage(
478                     AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED,
479                     songLen,
480                     currSongPosition);
481         }
482     }
483 
484     // Called by JNI on changes of play status
485     @VisibleForTesting
onPlayStatusChanged(BluetoothDevice device, int playbackState)486     synchronized void onPlayStatusChanged(BluetoothDevice device, int playbackState) {
487         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
488         if (stateMachine != null) {
489             stateMachine.sendMessage(
490                     AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
491         }
492     }
493 
494     // Called by JNI to report remote Player's capabilities
495     @VisibleForTesting
handlePlayerAppSetting( BluetoothDevice device, byte[] playerAttribRsp, int rspLen)496     synchronized void handlePlayerAppSetting(
497             BluetoothDevice device, byte[] playerAttribRsp, int rspLen) {
498         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
499         if (stateMachine != null) {
500             PlayerApplicationSettings supportedSettings =
501                     PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp);
502             stateMachine.sendMessage(
503                     AvrcpControllerStateMachine.MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS,
504                     supportedSettings);
505         }
506     }
507 
508     @VisibleForTesting
onPlayerAppSettingChanged( BluetoothDevice device, byte[] playerAttribRsp, int rspLen)509     synchronized void onPlayerAppSettingChanged(
510             BluetoothDevice device, byte[] playerAttribRsp, int rspLen) {
511         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
512         if (stateMachine != null) {
513 
514             PlayerApplicationSettings currentSettings =
515                     PlayerApplicationSettings.makeSettings(playerAttribRsp);
516             stateMachine.sendMessage(
517                     AvrcpControllerStateMachine.MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS,
518                     currentSettings);
519         }
520     }
521 
522     @VisibleForTesting
onAvailablePlayerChanged(BluetoothDevice device)523     void onAvailablePlayerChanged(BluetoothDevice device) {
524         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
525         if (stateMachine != null) {
526             stateMachine.sendMessage(
527                     AvrcpControllerStateMachine.MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED);
528         }
529     }
530 
531     // Browsing related JNI callbacks.
handleGetFolderItemsRsp(BluetoothDevice device, int status, AvrcpItem[] items)532     void handleGetFolderItemsRsp(BluetoothDevice device, int status, AvrcpItem[] items) {
533         Log.d(TAG, "handleGetFolderItemsRsp(device=" + device + ", status=" + status);
534         List<AvrcpItem> itemsList = new ArrayList<>();
535         for (AvrcpItem item : items) {
536             Log.v(TAG, "handleGetFolderItemsRsp(device=" + device + "): item=" + item.toString());
537             if (mCoverArtManager != null) {
538                 String handle = item.getCoverArtHandle();
539                 if (handle != null) {
540                     item.setCoverArtUuid(mCoverArtManager.getUuidForHandle(device, handle));
541                 }
542             }
543             itemsList.add(item);
544         }
545 
546         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
547         if (stateMachine != null) {
548             stateMachine.sendMessage(
549                     AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS, itemsList);
550         }
551     }
552 
handleGetPlayerItemsRsp(BluetoothDevice device, List<AvrcpPlayer> itemsList)553     void handleGetPlayerItemsRsp(BluetoothDevice device, List<AvrcpPlayer> itemsList) {
554         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
555         if (stateMachine != null) {
556             stateMachine.sendMessage(
557                     AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS, itemsList);
558         }
559     }
560 
561     @VisibleForTesting
handleChangeFolderRsp(BluetoothDevice device, int count)562     void handleChangeFolderRsp(BluetoothDevice device, int count) {
563         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
564         if (stateMachine != null) {
565             stateMachine.sendMessage(
566                     AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, count);
567         }
568     }
569 
570     @VisibleForTesting
handleSetBrowsedPlayerRsp(BluetoothDevice device, int items, int depth)571     void handleSetBrowsedPlayerRsp(BluetoothDevice device, int items, int depth) {
572         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
573         if (stateMachine != null) {
574             stateMachine.sendMessage(
575                     AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth);
576         }
577     }
578 
579     @VisibleForTesting
handleSetAddressedPlayerRsp(BluetoothDevice device, int status)580     void handleSetAddressedPlayerRsp(BluetoothDevice device, int status) {
581         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
582         if (stateMachine != null) {
583             stateMachine.sendMessage(
584                     AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
585         }
586     }
587 
588     @VisibleForTesting
handleAddressedPlayerChanged(BluetoothDevice device, int id)589     void handleAddressedPlayerChanged(BluetoothDevice device, int id) {
590         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
591         if (stateMachine != null) {
592             stateMachine.sendMessage(
593                     AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, id);
594         }
595     }
596 
597     @VisibleForTesting
handleNowPlayingContentChanged(BluetoothDevice device)598     void handleNowPlayingContentChanged(BluetoothDevice device) {
599         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
600         if (stateMachine != null) {
601             stateMachine.nowPlayingContentChanged();
602         }
603     }
604 
605     /* Generic Profile Code */
606 
607     /**
608      * Disconnect the given Bluetooth device.
609      *
610      * @return true if disconnect is successful, false otherwise.
611      */
disconnect(BluetoothDevice device)612     public synchronized boolean disconnect(BluetoothDevice device) {
613         Log.d(TAG, "disconnect(device=" + device + ")");
614         AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
615         // a map state machine instance doesn't exist. maybe it is already gone?
616         if (stateMachine == null) {
617             return false;
618         }
619         int connectionState = stateMachine.getState();
620         if (connectionState != STATE_CONNECTED && connectionState != STATE_CONNECTING) {
621             return false;
622         }
623         stateMachine.disconnect();
624         return true;
625     }
626 
627     /** Remove state machine from device map once it is no longer needed. */
removeStateMachine(AvrcpControllerStateMachine stateMachine)628     public void removeStateMachine(AvrcpControllerStateMachine stateMachine) {
629         if (stateMachine == null) {
630             return;
631         }
632         BluetoothDevice device = stateMachine.getDevice();
633         if (device.equals(getActiveDevice())) {
634             setActiveDevice(null);
635         }
636         mDeviceStateMap.remove(stateMachine.getDevice());
637         stateMachine.quitNow();
638     }
639 
getConnectedDevices()640     public List<BluetoothDevice> getConnectedDevices() {
641         return getDevicesMatchingConnectionStates(new int[] {BluetoothAdapter.STATE_CONNECTED});
642     }
643 
getStateMachine(BluetoothDevice device)644     protected AvrcpControllerStateMachine getStateMachine(BluetoothDevice device) {
645         if (device == null) {
646             return null;
647         }
648         return mDeviceStateMap.get(device);
649     }
650 
getOrCreateStateMachine(BluetoothDevice device)651     protected AvrcpControllerStateMachine getOrCreateStateMachine(BluetoothDevice device) {
652         AvrcpControllerStateMachine newStateMachine =
653                 new AvrcpControllerStateMachine(
654                         mAdapterService,
655                         this,
656                         device,
657                         mNativeInterface,
658                         Utils.isAutomotive(getApplicationContext()));
659         AvrcpControllerStateMachine existingStateMachine =
660                 mDeviceStateMap.putIfAbsent(device, newStateMachine);
661         // Given null is not a valid value in our map, ConcurrentHashMap will return null if the
662         // key was absent and our new value was added. We should then start and return it. Else
663         // we quit the new one so we don't leak a thread
664         if (existingStateMachine == null) {
665             newStateMachine.start();
666             return newStateMachine;
667         } else {
668             // If you try to quit a StateMachine that hasn't been constructed yet, the StateMachine
669             // spits out an NPE trying to read a state stack array that only gets made on start().
670             // We can just quit the thread made explicitly
671             newStateMachine.getHandler().getLooper().quit();
672         }
673         return existingStateMachine;
674     }
675 
getCoverArtManager()676     protected AvrcpCoverArtManager getCoverArtManager() {
677         return mCoverArtManager;
678     }
679 
getDevicesMatchingConnectionStates(int[] states)680     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
681         Log.d(TAG, "getDevicesMatchingConnectionStates(states=" + Arrays.toString(states) + ")");
682         List<BluetoothDevice> deviceList = new ArrayList<>();
683         BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
684         int connectionState;
685         for (BluetoothDevice device : bondedDevices) {
686             connectionState = getConnectionState(device);
687             for (int i = 0; i < states.length; i++) {
688                 if (connectionState == states[i]) {
689                     deviceList.add(device);
690                 }
691             }
692         }
693         Log.d(
694                 TAG,
695                 "getDevicesMatchingConnectionStates(states="
696                         + Arrays.toString(states)
697                         + "): Found "
698                         + deviceList.toString());
699         return deviceList;
700     }
701 
getConnectionState(BluetoothDevice device)702     synchronized int getConnectionState(BluetoothDevice device) {
703         AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
704         return (stateMachine == null) ? STATE_DISCONNECTED : stateMachine.getState();
705     }
706 
707     @Override
dump(StringBuilder sb)708     public void dump(StringBuilder sb) {
709         super.dump(sb);
710         ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size());
711         ProfileService.println(sb, "Active Device = " + mActiveDevice);
712 
713         for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
714             ProfileService.println(
715                     sb, "==== StateMachine for " + stateMachine.getDevice() + " ====");
716             stateMachine.dump(sb);
717         }
718         sb.append("\n  BrowseTree:\n");
719         mBrowseTree.dump(sb);
720 
721         sb.append("\n  Cover Artwork Enabled: ").append((mCoverArtEnabled ? "True" : "False"));
722         if (mCoverArtManager != null) {
723             sb.append("\n  ").append(mCoverArtManager.toString());
724         }
725 
726         sb.append("\n  ").append(BluetoothMediaBrowserService.dump()).append("\n");
727     }
728 }
729