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