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