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