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.BluetoothAdapter;
20 import android.bluetooth.BluetoothAvrcpPlayerSettings;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothProfile;
23 import android.bluetooth.IBluetoothAvrcpController;
24 import android.content.Intent;
25 import android.support.v4.media.MediaBrowserCompat.MediaItem;
26 import android.support.v4.media.session.PlaybackStateCompat;
27 import android.util.Log;
28 
29 import com.android.bluetooth.R;
30 import com.android.bluetooth.Utils;
31 import com.android.bluetooth.btservice.ProfileService;
32 
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.UUID;
39 import java.util.concurrent.ConcurrentHashMap;
40 
41 /**
42  * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
43  */
44 public class AvrcpControllerService extends ProfileService {
45     static final String TAG = "AvrcpControllerService";
46     static final int MAXIMUM_CONNECTED_DEVICES = 5;
47     static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
48     static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
49 
50     /*
51      *  Play State Values from JNI
52      */
53     private static final byte JNI_PLAY_STATUS_STOPPED = 0x00;
54     private static final byte JNI_PLAY_STATUS_PLAYING = 0x01;
55     private static final byte JNI_PLAY_STATUS_PAUSED = 0x02;
56     private static final byte JNI_PLAY_STATUS_FWD_SEEK = 0x03;
57     private static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04;
58     private static final byte JNI_PLAY_STATUS_ERROR = -1;
59 
60     /* Folder/Media Item scopes.
61      * Keep in sync with AVRCP 1.6 sec. 6.10.1
62      */
63     public static final byte BROWSE_SCOPE_PLAYER_LIST = 0x00;
64     public static final byte BROWSE_SCOPE_VFS = 0x01;
65     public static final byte BROWSE_SCOPE_SEARCH = 0x02;
66     public static final byte BROWSE_SCOPE_NOW_PLAYING = 0x03;
67 
68     /* Folder navigation directions
69      * This is borrowed from AVRCP 1.6 spec and must be kept with same values
70      */
71     public static final byte FOLDER_NAVIGATION_DIRECTION_UP = 0x00;
72     public static final byte FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01;
73 
74     /*
75      * KeyCoded for Pass Through Commands
76      */
77     public static final int PASS_THRU_CMD_ID_PLAY = 0x44;
78     public static final int PASS_THRU_CMD_ID_PAUSE = 0x46;
79     public static final int PASS_THRU_CMD_ID_VOL_UP = 0x41;
80     public static final int PASS_THRU_CMD_ID_VOL_DOWN = 0x42;
81     public static final int PASS_THRU_CMD_ID_STOP = 0x45;
82     public static final int PASS_THRU_CMD_ID_FF = 0x49;
83     public static final int PASS_THRU_CMD_ID_REWIND = 0x48;
84     public static final int PASS_THRU_CMD_ID_FORWARD = 0x4B;
85     public static final int PASS_THRU_CMD_ID_BACKWARD = 0x4C;
86 
87     /* Key State Variables */
88     public static final int KEY_STATE_PRESSED = 0;
89     public static final int KEY_STATE_RELEASED = 1;
90 
91     static BrowseTree sBrowseTree;
92     private static AvrcpControllerService sService;
93     private final BluetoothAdapter mAdapter;
94 
95     protected Map<BluetoothDevice, AvrcpControllerStateMachine> mDeviceStateMap =
96             new ConcurrentHashMap<>(1);
97 
98     private boolean mCoverArtEnabled = false;
99     protected AvrcpCoverArtManager mCoverArtManager;
100 
101     private class ImageDownloadCallback implements AvrcpCoverArtManager.Callback {
102         @Override
onImageDownloadComplete(BluetoothDevice device, AvrcpCoverArtManager.DownloadEvent event)103         public void onImageDownloadComplete(BluetoothDevice device,
104                 AvrcpCoverArtManager.DownloadEvent event) {
105             if (DBG) {
106                 Log.d(TAG, "Image downloaded [device: " + device + ", uuid: " + event.getUuid()
107                         + ", uri: " + event.getUri());
108             }
109             AvrcpControllerStateMachine stateMachine = getStateMachine(device);
110             if (stateMachine == null) {
111                 Log.e(TAG, "No state machine found for device " + device);
112                 mCoverArtManager.removeImage(device, event.getUuid());
113                 return;
114             }
115             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_IMAGE_DOWNLOADED,
116                     event);
117         }
118     }
119 
120     static {
classInitNative()121         classInitNative();
122     }
123 
AvrcpControllerService()124     public AvrcpControllerService() {
125         super();
126         mAdapter = BluetoothAdapter.getDefaultAdapter();
127     }
128 
129     @Override
start()130     protected synchronized boolean start() {
131         initNative();
132         mCoverArtEnabled = getResources().getBoolean(R.bool.avrcp_controller_enable_cover_art);
133         if (mCoverArtEnabled) {
134             mCoverArtManager = new AvrcpCoverArtManager(this, new ImageDownloadCallback());
135         }
136         sBrowseTree = new BrowseTree(null);
137         sService = this;
138 
139         // Start the media browser service.
140         Intent startIntent = new Intent(this, BluetoothMediaBrowserService.class);
141         startService(startIntent);
142         return true;
143     }
144 
145     @Override
stop()146     protected synchronized boolean stop() {
147         Intent stopIntent = new Intent(this, BluetoothMediaBrowserService.class);
148         stopService(stopIntent);
149         for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
150             stateMachine.quitNow();
151         }
152 
153         sService = null;
154         sBrowseTree = null;
155         if (mCoverArtManager != null) {
156             mCoverArtManager.cleanup();
157             mCoverArtManager = null;
158         }
159         return true;
160     }
161 
getAvrcpControllerService()162     public static AvrcpControllerService getAvrcpControllerService() {
163         return sService;
164     }
165 
newStateMachine(BluetoothDevice device)166     protected AvrcpControllerStateMachine newStateMachine(BluetoothDevice device) {
167         return new AvrcpControllerStateMachine(device, this);
168     }
169 
getCurrentMetadataIfNoCoverArt(BluetoothDevice device)170     protected void getCurrentMetadataIfNoCoverArt(BluetoothDevice device) {
171         if (device == null) return;
172         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
173         if (stateMachine == null) return;
174         AvrcpItem track = stateMachine.getCurrentTrack();
175         if (track != null && track.getCoverArtLocation() == null) {
176             getCurrentMetadataNative(Utils.getByteAddress(device));
177         }
178     }
179 
refreshContents(BrowseTree.BrowseNode node)180     private void refreshContents(BrowseTree.BrowseNode node) {
181         BluetoothDevice device = node.getDevice();
182         if (device == null) {
183             return;
184         }
185         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
186         if (stateMachine != null) {
187             stateMachine.requestContents(node);
188         }
189     }
190 
playItem(String parentMediaId)191     void playItem(String parentMediaId) {
192         if (DBG) Log.d(TAG, "playItem(" + parentMediaId + ")");
193         // Check if the requestedNode is a player rather than a song
194         BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId);
195         if (requestedNode == null) {
196             for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
197                 // Check each state machine for the song and then play it
198                 requestedNode = stateMachine.findNode(parentMediaId);
199                 if (requestedNode != null) {
200                     if (DBG) Log.d(TAG, "Found a node");
201                     stateMachine.playItem(requestedNode);
202                     break;
203                 }
204             }
205         }
206     }
207 
208     /*Java API*/
209 
210     /**
211      * Get a List of MediaItems that are children of the specified media Id
212      *
213      * @param parentMediaId The player or folder to get the contents of
214      * @return List of Children if available, an empty list if there are none,
215      * or null if a search must be performed.
216      */
getContents(String parentMediaId)217     public synchronized List<MediaItem> getContents(String parentMediaId) {
218         if (DBG) Log.d(TAG, "getContents(" + parentMediaId + ")");
219 
220         BrowseTree.BrowseNode requestedNode = sBrowseTree.findBrowseNodeByID(parentMediaId);
221         if (requestedNode == null) {
222             for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
223                 requestedNode = stateMachine.findNode(parentMediaId);
224                 if (requestedNode != null) {
225                     Log.d(TAG, "Found a node");
226                     break;
227                 }
228             }
229         }
230 
231         // If we don't find a node in the tree then do not have any way to browse for the contents.
232         // Return an empty list instead.
233         if (requestedNode == null) {
234             if (DBG) Log.d(TAG, "Didn't find a node");
235             return new ArrayList(0);
236         } else {
237             if (!requestedNode.isCached()) {
238                 if (DBG) Log.d(TAG, "node is not cached");
239                 refreshContents(requestedNode);
240             }
241             if (DBG) Log.d(TAG, "Returning contents");
242             return requestedNode.getContents();
243         }
244     }
245 
246     @Override
initBinder()247     protected IProfileServiceBinder initBinder() {
248         return new AvrcpControllerServiceBinder(this);
249     }
250 
251     //Binder object: Must be static class or memory leak may occur
252     private static class AvrcpControllerServiceBinder extends IBluetoothAvrcpController.Stub
253             implements IProfileServiceBinder {
254         private AvrcpControllerService mService;
255 
getService()256         private AvrcpControllerService getService() {
257             if (!Utils.checkCaller()) {
258                 Log.w(TAG, "AVRCP call not allowed for non-active user");
259                 return null;
260             }
261 
262             if (mService != null) {
263                 return mService;
264             }
265             return null;
266         }
267 
AvrcpControllerServiceBinder(AvrcpControllerService service)268         AvrcpControllerServiceBinder(AvrcpControllerService service) {
269             mService = service;
270         }
271 
272         @Override
cleanup()273         public void cleanup() {
274             mService = null;
275         }
276 
277         @Override
getConnectedDevices()278         public List<BluetoothDevice> getConnectedDevices() {
279             AvrcpControllerService service = getService();
280             if (service == null) {
281                 return new ArrayList<BluetoothDevice>(0);
282             }
283             return service.getConnectedDevices();
284         }
285 
286         @Override
getDevicesMatchingConnectionStates(int[] states)287         public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
288             AvrcpControllerService service = getService();
289             if (service == null) {
290                 return new ArrayList<BluetoothDevice>(0);
291             }
292             return service.getDevicesMatchingConnectionStates(states);
293         }
294 
295         @Override
getConnectionState(BluetoothDevice device)296         public int getConnectionState(BluetoothDevice device) {
297             AvrcpControllerService service = getService();
298             if (service == null) {
299                 return BluetoothProfile.STATE_DISCONNECTED;
300             }
301             return service.getConnectionState(device);
302         }
303 
304         @Override
sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState)305         public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
306             Log.w(TAG, "sendGroupNavigationCmd not implemented");
307             return;
308         }
309 
310         @Override
setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings settings)311         public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings settings) {
312             Log.w(TAG, "setPlayerApplicationSetting not implemented");
313             return false;
314         }
315 
316         @Override
getPlayerSettings(BluetoothDevice device)317         public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
318             Log.w(TAG, "getPlayerSettings not implemented");
319             return null;
320         }
321     }
322 
323 
324     /* JNI API*/
325     // Called by JNI when a passthrough key was received.
handlePassthroughRsp(int id, int keyState, byte[] address)326     private void handlePassthroughRsp(int id, int keyState, byte[] address) {
327         if (DBG) {
328             Log.d(TAG, "passthrough response received as: key: " + id
329                     + " state: " + keyState + "address:" + address);
330         }
331     }
332 
handleGroupNavigationRsp(int id, int keyState)333     private void handleGroupNavigationRsp(int id, int keyState) {
334         if (DBG) {
335             Log.d(TAG, "group navigation response received as: key: " + id + " state: "
336                     + keyState);
337         }
338     }
339 
340     // Called by JNI when a device has connected or disconnected.
onConnectionStateChanged(boolean remoteControlConnected, boolean browsingConnected, byte[] address)341     private synchronized void onConnectionStateChanged(boolean remoteControlConnected,
342             boolean browsingConnected, byte[] address) {
343         BluetoothDevice device = mAdapter.getRemoteDevice(address);
344         if (DBG) {
345             Log.d(TAG, "onConnectionStateChanged " + remoteControlConnected + " "
346                     + browsingConnected + device);
347         }
348         if (device == null) {
349             Log.e(TAG, "onConnectionStateChanged Device is null");
350             return;
351         }
352 
353         StackEvent event =
354                 StackEvent.connectionStateChanged(remoteControlConnected, browsingConnected);
355         AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device);
356         if (remoteControlConnected || browsingConnected) {
357             stateMachine.connect(event);
358         } else {
359             stateMachine.disconnect();
360         }
361     }
362 
363     // Called by JNI to notify Avrcp of features supported by the Remote device.
getRcFeatures(byte[] address, int features)364     private void getRcFeatures(byte[] address, int features) {
365         /* Do Nothing. */
366     }
367 
368     // Called by JNI to notify Avrcp of a remote device's Cover Art PSM
getRcPsm(byte[] address, int psm)369     private void getRcPsm(byte[] address, int psm) {
370         BluetoothDevice device = mAdapter.getRemoteDevice(address);
371         if (DBG) Log.d(TAG, "getRcPsm(device=" + device + ", psm=" + psm + ")");
372         AvrcpControllerStateMachine stateMachine = getOrCreateStateMachine(device);
373         if (stateMachine != null) {
374             stateMachine.sendMessage(
375                     AvrcpControllerStateMachine.MESSAGE_PROCESS_RECEIVED_COVER_ART_PSM, psm);
376         }
377     }
378 
379     // Called by JNI
setPlayerAppSettingRsp(byte[] address, byte accepted)380     private void setPlayerAppSettingRsp(byte[] address, byte accepted) {
381         /* Do Nothing. */
382     }
383 
384     // Called by JNI when remote wants to receive absolute volume notifications.
handleRegisterNotificationAbsVol(byte[] address, byte label)385     private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) {
386         if (DBG) {
387             Log.d(TAG, "handleRegisterNotificationAbsVol");
388         }
389         BluetoothDevice device = mAdapter.getRemoteDevice(address);
390         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
391         if (stateMachine != null) {
392             stateMachine.sendMessage(
393                     AvrcpControllerStateMachine.MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION);
394         }
395     }
396 
397     // Called by JNI when remote wants to set absolute volume.
handleSetAbsVolume(byte[] address, byte absVol, byte label)398     private synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) {
399         if (DBG) {
400             Log.d(TAG, "handleSetAbsVolume ");
401         }
402         BluetoothDevice device = mAdapter.getRemoteDevice(address);
403         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
404         if (stateMachine != null) {
405             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD,
406                     absVol);
407         }
408     }
409 
410     // Called by JNI when a track changes and local AvrcpController is registered for updates.
onTrackChanged(byte[] address, byte numAttributes, int[] attributes, String[] attribVals)411     private synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
412             String[] attribVals) {
413         if (DBG) {
414             Log.d(TAG, "onTrackChanged");
415         }
416 
417         BluetoothDevice device = mAdapter.getRemoteDevice(address);
418         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
419         if (stateMachine != null) {
420             AvrcpItem.Builder aib = new AvrcpItem.Builder();
421             aib.fromAvrcpAttributeArray(attributes, attribVals);
422             aib.setDevice(device);
423             aib.setItemType(AvrcpItem.TYPE_MEDIA);
424             aib.setUuid(UUID.randomUUID().toString());
425             AvrcpItem item = aib.build();
426             if (mCoverArtManager != null) {
427                 String handle = item.getCoverArtHandle();
428                 if (handle != null) {
429                     item.setCoverArtUuid(mCoverArtManager.getUuidForHandle(device, handle));
430                 }
431             }
432             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_TRACK_CHANGED,
433                     item);
434         }
435     }
436 
437     // Called by JNI periodically based upon timer to update play position
onPlayPositionChanged(byte[] address, int songLen, int currSongPosition)438     private synchronized void onPlayPositionChanged(byte[] address, int songLen,
439             int currSongPosition) {
440         if (DBG) {
441             Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
442         }
443         BluetoothDevice device = mAdapter.getRemoteDevice(address);
444         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
445         if (stateMachine != null) {
446             stateMachine.sendMessage(
447                     AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_POS_CHANGED,
448                     songLen, currSongPosition);
449         }
450     }
451 
452     // Called by JNI on changes of play status
onPlayStatusChanged(byte[] address, byte playStatus)453     private synchronized void onPlayStatusChanged(byte[] address, byte playStatus) {
454         if (DBG) {
455             Log.d(TAG, "onPlayStatusChanged " + playStatus);
456         }
457         int playbackState = PlaybackStateCompat.STATE_NONE;
458         switch (playStatus) {
459             case JNI_PLAY_STATUS_STOPPED:
460                 playbackState = PlaybackStateCompat.STATE_STOPPED;
461                 break;
462             case JNI_PLAY_STATUS_PLAYING:
463                 playbackState = PlaybackStateCompat.STATE_PLAYING;
464                 break;
465             case JNI_PLAY_STATUS_PAUSED:
466                 playbackState = PlaybackStateCompat.STATE_PAUSED;
467                 break;
468             case JNI_PLAY_STATUS_FWD_SEEK:
469                 playbackState = PlaybackStateCompat.STATE_FAST_FORWARDING;
470                 break;
471             case JNI_PLAY_STATUS_REV_SEEK:
472                 playbackState = PlaybackStateCompat.STATE_REWINDING;
473                 break;
474             default:
475                 playbackState = PlaybackStateCompat.STATE_NONE;
476         }
477         BluetoothDevice device = mAdapter.getRemoteDevice(address);
478         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
479         if (stateMachine != null) {
480             stateMachine.sendMessage(
481                     AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
482         }
483     }
484 
485     // Called by JNI to report remote Player's capabilities
handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen)486     private synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp,
487             int rspLen) {
488         if (DBG) {
489             Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
490         }
491         BluetoothDevice device = mAdapter.getRemoteDevice(address);
492         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
493         if (stateMachine != null) {
494             PlayerApplicationSettings supportedSettings =
495                     PlayerApplicationSettings.makeSupportedSettings(playerAttribRsp);
496             stateMachine.sendMessage(
497                     AvrcpControllerStateMachine.MESSAGE_PROCESS_SUPPORTED_APPLICATION_SETTINGS,
498                     supportedSettings);
499         }
500     }
501 
onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen)502     private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp,
503             int rspLen) {
504         if (DBG) {
505             Log.d(TAG, "onPlayerAppSettingChanged ");
506         }
507         BluetoothDevice device = mAdapter.getRemoteDevice(address);
508         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
509         if (stateMachine != null) {
510 
511             PlayerApplicationSettings currentSettings =
512                     PlayerApplicationSettings.makeSettings(playerAttribRsp);
513             stateMachine.sendMessage(
514                     AvrcpControllerStateMachine.MESSAGE_PROCESS_CURRENT_APPLICATION_SETTINGS,
515                     currentSettings);
516         }
517     }
518 
onAvailablePlayerChanged(byte[] address)519     private void onAvailablePlayerChanged(byte[] address) {
520         if (DBG) {
521             Log.d(TAG," onAvailablePlayerChanged");
522         }
523         BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
524 
525         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
526         if (stateMachine != null) {
527             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_AVAILABLE_PLAYER_CHANGED);
528         }
529     }
530 
531     // Browsing related JNI callbacks.
handleGetFolderItemsRsp(byte[] address, int status, AvrcpItem[] items)532     void handleGetFolderItemsRsp(byte[] address, int status, AvrcpItem[] items) {
533         if (DBG) {
534             Log.d(TAG, "handleGetFolderItemsRsp called with status " + status + " items "
535                     + items.length + " items.");
536         }
537 
538         BluetoothDevice device = mAdapter.getRemoteDevice(address);
539         List<AvrcpItem> itemsList = new ArrayList<>();
540         for (AvrcpItem item : items) {
541             if (VDBG) Log.d(TAG, item.toString());
542             if (mCoverArtManager != null) {
543                 String handle = item.getCoverArtHandle();
544                 if (handle != null) {
545                     item.setCoverArtUuid(mCoverArtManager.getUuidForHandle(device, handle));
546                 }
547             }
548             itemsList.add(item);
549         }
550 
551         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
552         if (stateMachine != null) {
553             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_FOLDER_ITEMS,
554                     itemsList);
555         }
556     }
557 
handleGetPlayerItemsRsp(byte[] address, AvrcpPlayer[] items)558     void handleGetPlayerItemsRsp(byte[] address, AvrcpPlayer[] items) {
559         if (DBG) {
560             Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
561         }
562 
563         List<AvrcpPlayer> itemsList = new ArrayList<>();
564         for (AvrcpPlayer item : items) {
565             if (VDBG) Log.d(TAG, "bt player item: " + item);
566             itemsList.add(item);
567         }
568 
569         BluetoothDevice device = mAdapter.getRemoteDevice(address);
570         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
571         if (stateMachine != null) {
572             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS,
573                     itemsList);
574         }
575     }
576 
577     // JNI Helper functions to convert native objects to java.
createFromNativeMediaItem(byte[] address, long uid, int type, String name, int[] attrIds, String[] attrVals)578     AvrcpItem createFromNativeMediaItem(byte[] address, long uid, int type, String name,
579             int[] attrIds, String[] attrVals) {
580         if (VDBG) {
581             Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type: " + type + " name: " + name
582                     + " attrids: " + attrIds + " attrVals: " + attrVals);
583         }
584 
585         BluetoothDevice device = mAdapter.getRemoteDevice(address);
586         AvrcpItem.Builder aib = new AvrcpItem.Builder().fromAvrcpAttributeArray(attrIds, attrVals);
587         aib.setDevice(device);
588         aib.setItemType(AvrcpItem.TYPE_MEDIA);
589         aib.setType(type);
590         aib.setUid(uid);
591         aib.setUuid(UUID.randomUUID().toString());
592         aib.setPlayable(true);
593         AvrcpItem item = aib.build();
594         return item;
595     }
596 
createFromNativeFolderItem(byte[] address, long uid, int type, String name, int playable)597     AvrcpItem createFromNativeFolderItem(byte[] address, long uid, int type, String name,
598             int playable) {
599         if (VDBG) {
600             Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type + " name "
601                     + name + " playable " + playable);
602         }
603 
604         BluetoothDevice device = mAdapter.getRemoteDevice(address);
605         AvrcpItem.Builder aib = new AvrcpItem.Builder();
606         aib.setDevice(device);
607         aib.setItemType(AvrcpItem.TYPE_FOLDER);
608         aib.setType(type);
609         aib.setUid(uid);
610         aib.setUuid(UUID.randomUUID().toString());
611         aib.setDisplayableName(name);
612         aib.setPlayable(playable == 0x01);
613         aib.setBrowsable(true);
614         return aib.build();
615     }
616 
createFromNativePlayerItem(byte[] address, int id, String name, byte[] transportFlags, int playStatus, int playerType)617     AvrcpPlayer createFromNativePlayerItem(byte[] address, int id, String name,
618             byte[] transportFlags, int playStatus, int playerType) {
619         if (VDBG) {
620             Log.d(TAG,
621                     "createFromNativePlayerItem name: " + name + " transportFlags "
622                             + transportFlags + " play status " + playStatus + " player type "
623                             + playerType);
624         }
625         BluetoothDevice device = mAdapter.getRemoteDevice(address);
626         AvrcpPlayer player = new AvrcpPlayer(device, id, name, transportFlags, playStatus,
627                 playerType);
628         return player;
629     }
630 
handleChangeFolderRsp(byte[] address, int count)631     private void handleChangeFolderRsp(byte[] address, int count) {
632         if (DBG) {
633             Log.d(TAG, "handleChangeFolderRsp count: " + count);
634         }
635         BluetoothDevice device = mAdapter.getRemoteDevice(address);
636         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
637         if (stateMachine != null) {
638             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH,
639                     count);
640         }
641     }
642 
handleSetBrowsedPlayerRsp(byte[] address, int items, int depth)643     private void handleSetBrowsedPlayerRsp(byte[] address, int items, int depth) {
644         if (DBG) {
645             Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
646         }
647         BluetoothDevice device = mAdapter.getRemoteDevice(address);
648 
649         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
650         if (stateMachine != null) {
651             stateMachine.sendMessage(AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER,
652                     items, depth);
653         }
654     }
655 
handleSetAddressedPlayerRsp(byte[] address, int status)656     private void handleSetAddressedPlayerRsp(byte[] address, int status) {
657         if (DBG) {
658             Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
659         }
660         BluetoothDevice device = mAdapter.getRemoteDevice(address);
661 
662         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
663         if (stateMachine != null) {
664             stateMachine.sendMessage(
665                     AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
666         }
667     }
668 
handleAddressedPlayerChanged(byte[] address, int id)669     private void handleAddressedPlayerChanged(byte[] address, int id) {
670         if (DBG) {
671             Log.d(TAG, "handleAddressedPlayerChanged id: " + id);
672         }
673         BluetoothDevice device = mAdapter.getRemoteDevice(address);
674 
675         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
676         if (stateMachine != null) {
677             stateMachine.sendMessage(
678                     AvrcpControllerStateMachine.MESSAGE_PROCESS_ADDRESSED_PLAYER_CHANGED, id);
679         }
680     }
681 
handleNowPlayingContentChanged(byte[] address)682     private void handleNowPlayingContentChanged(byte[] address) {
683         if (DBG) {
684             Log.d(TAG, "handleNowPlayingContentChanged");
685         }
686         BluetoothDevice device = mAdapter.getRemoteDevice(address);
687 
688         AvrcpControllerStateMachine stateMachine = getStateMachine(device);
689         if (stateMachine != null) {
690             stateMachine.nowPlayingContentChanged();
691         }
692     }
693 
694     /* Generic Profile Code */
695 
696     /**
697      * Disconnect the given Bluetooth device.
698      *
699      * @return true if disconnect is successful, false otherwise.
700      */
disconnect(BluetoothDevice device)701     public synchronized boolean disconnect(BluetoothDevice device) {
702         if (DBG) {
703             StringBuilder sb = new StringBuilder();
704             dump(sb);
705             Log.d(TAG, "MAP disconnect device: " + device
706                     + ", InstanceMap start state: " + sb.toString());
707         }
708         AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
709         // a map state machine instance doesn't exist. maybe it is already gone?
710         if (stateMachine == null) {
711             return false;
712         }
713         int connectionState = stateMachine.getState();
714         if (connectionState != BluetoothProfile.STATE_CONNECTED
715                 && connectionState != BluetoothProfile.STATE_CONNECTING) {
716             return false;
717         }
718         stateMachine.disconnect();
719         if (DBG) {
720             StringBuilder sb = new StringBuilder();
721             dump(sb);
722             Log.d(TAG, "MAP disconnect device: " + device
723                     + ", InstanceMap start state: " + sb.toString());
724         }
725         return true;
726     }
727 
728     /**
729      * Remove state machine from device map once it is no longer needed.
730      */
removeStateMachine(AvrcpControllerStateMachine stateMachine)731     public void removeStateMachine(AvrcpControllerStateMachine stateMachine) {
732         mDeviceStateMap.remove(stateMachine.getDevice());
733     }
734 
getConnectedDevices()735     public List<BluetoothDevice> getConnectedDevices() {
736         return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED});
737     }
738 
getStateMachine(BluetoothDevice device)739     protected AvrcpControllerStateMachine getStateMachine(BluetoothDevice device) {
740         return mDeviceStateMap.get(device);
741     }
742 
getOrCreateStateMachine(BluetoothDevice device)743     protected AvrcpControllerStateMachine getOrCreateStateMachine(BluetoothDevice device) {
744         AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
745         if (stateMachine == null) {
746             stateMachine = newStateMachine(device);
747             mDeviceStateMap.put(device, stateMachine);
748             stateMachine.start();
749         }
750         return stateMachine;
751     }
752 
getCoverArtManager()753     protected AvrcpCoverArtManager getCoverArtManager() {
754         return mCoverArtManager;
755     }
756 
getDevicesMatchingConnectionStates(int[] states)757     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
758         if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
759         List<BluetoothDevice> deviceList = new ArrayList<>();
760         Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
761         int connectionState;
762         for (BluetoothDevice device : bondedDevices) {
763             connectionState = getConnectionState(device);
764             if (DBG) Log.d(TAG, "Device: " + device + "State: " + connectionState);
765             for (int i = 0; i < states.length; i++) {
766                 if (connectionState == states[i]) {
767                     deviceList.add(device);
768                 }
769             }
770         }
771         if (DBG) Log.d(TAG, deviceList.toString());
772         Log.d(TAG, "GetDevicesDone");
773         return deviceList;
774     }
775 
getConnectionState(BluetoothDevice device)776     synchronized int getConnectionState(BluetoothDevice device) {
777         AvrcpControllerStateMachine stateMachine = mDeviceStateMap.get(device);
778         return (stateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED
779                 : stateMachine.getState();
780     }
781 
782     @Override
dump(StringBuilder sb)783     public void dump(StringBuilder sb) {
784         super.dump(sb);
785         ProfileService.println(sb, "Devices Tracked = " + mDeviceStateMap.size());
786 
787         for (AvrcpControllerStateMachine stateMachine : mDeviceStateMap.values()) {
788             ProfileService.println(sb,
789                     "==== StateMachine for " + stateMachine.getDevice() + " ====");
790             stateMachine.dump(sb);
791         }
792         sb.append("\n  sBrowseTree: " + sBrowseTree.toString());
793 
794         sb.append("\n  Cover Artwork Enabled: " + (mCoverArtEnabled ? "True" : "False"));
795         if (mCoverArtManager != null) {
796             sb.append("\n  " + mCoverArtManager.toString());
797         }
798     }
799 
800     /*JNI*/
classInitNative()801     private static native void classInitNative();
802 
initNative()803     private native void initNative();
804 
cleanupNative()805     private native void cleanupNative();
806 
807     /**
808      * Send button press commands to addressed device
809      *
810      * @param keyCode  key code as defined in AVRCP specification
811      * @param keyState 0 = key pressed, 1 = key released
812      * @return command was sent
813      */
sendPassThroughCommandNative(byte[] address, int keyCode, int keyState)814     public native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
815 
816     /**
817      * Send group navigation commands
818      *
819      * @param keyCode  next/previous
820      * @param keyState state
821      * @return command was sent
822      */
sendGroupNavigationCommandNative(byte[] address, int keyCode, int keyState)823     public native boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
824             int keyState);
825 
826     /**
827      * Change player specific settings such as shuffle
828      *
829      * @param numAttrib number of settings being sent
830      * @param attribIds list of settings to be changed
831      * @param attribVal list of settings values
832      */
setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib, byte[] attribIds, byte[] attribVal)833     public native void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
834             byte[] attribIds, byte[] attribVal);
835 
836     /**
837      * Send response to set absolute volume
838      *
839      * @param absVol new volume
840      * @param label  label
841      */
sendAbsVolRspNative(byte[] address, int absVol, int label)842     public native void sendAbsVolRspNative(byte[] address, int absVol, int label);
843 
844     /**
845      * Register for any volume level changes
846      *
847      * @param rspType type of response
848      * @param absVol  current volume
849      * @param label   label
850      */
sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol, int label)851     public native void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
852             int label);
853 
854     /**
855      * Fetch the current track's metadata
856      *
857      * This method is specifically meant to allow us to fetch image handles that may not have been
858      * sent to us yet, prior to having a BIP client connection. See the AVRCP 1.6+ specification,
859      * section 4.1.7, for more details.
860      */
getCurrentMetadataNative(byte[] address)861     public native void getCurrentMetadataNative(byte[] address);
862 
863     /**
864      * Fetch the playback state
865      */
getPlaybackStateNative(byte[] address)866     public native void getPlaybackStateNative(byte[] address);
867 
868     /**
869      * Fetch the current now playing list
870      *
871      * @param start first index to retrieve
872      * @param end   last index to retrieve
873      */
getNowPlayingListNative(byte[] address, int start, int end)874     public native void getNowPlayingListNative(byte[] address, int start, int end);
875 
876     /**
877      * Fetch the current folder's listing
878      *
879      * @param start first index to retrieve
880      * @param end   last index to retrieve
881      */
getFolderListNative(byte[] address, int start, int end)882     public native void getFolderListNative(byte[] address, int start, int end);
883 
884     /**
885      * Fetch the listing of players
886      *
887      * @param start first index to retrieve
888      * @param end   last index to retrieve
889      */
getPlayerListNative(byte[] address, int start, int end)890     public native void getPlayerListNative(byte[] address, int start, int end);
891 
892     /**
893      * Change the current browsed folder
894      *
895      * @param direction up/down
896      * @param uid       folder unique id
897      */
changeFolderPathNative(byte[] address, byte direction, long uid)898     public native void changeFolderPathNative(byte[] address, byte direction, long uid);
899 
900     /**
901      * Play item with provided uid
902      *
903      * @param scope      scope of item to played
904      * @param uid        song unique id
905      * @param uidCounter counter
906      */
playItemNative(byte[] address, byte scope, long uid, int uidCounter)907     public native void playItemNative(byte[] address, byte scope, long uid, int uidCounter);
908 
909     /**
910      * Set a specific player for browsing
911      *
912      * @param playerId player number
913      */
setBrowsedPlayerNative(byte[] address, int playerId)914     public native void setBrowsedPlayerNative(byte[] address, int playerId);
915 
916     /**
917      * Set a specific player for handling playback commands
918      *
919      * @param playerId player number
920      */
setAddressedPlayerNative(byte[] address, int playerId)921     public native void setAddressedPlayerNative(byte[] address, int playerId);
922 }
923