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