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