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