AvrcpControllerService.java revision ea14e69d8b1c7293869c0e4ae9180f36149a95b7
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
17package com.android.bluetooth.avrcpcontroller;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothAvrcpPlayerSettings;
21import android.bluetooth.BluetoothDevice;
22import android.bluetooth.BluetoothProfile;
23import android.bluetooth.IBluetoothAvrcpController;
24import android.content.Context;
25import android.media.AudioManager;
26import android.media.browse.MediaBrowser;
27import android.media.browse.MediaBrowser.MediaItem;
28import android.media.MediaDescription;
29import android.media.MediaMetadata;
30import android.media.session.PlaybackState;
31import android.os.Bundle;
32import android.os.HandlerThread;
33import android.os.Looper;
34import android.os.Message;
35import android.util.Log;
36
37import com.android.bluetooth.Utils;
38import com.android.bluetooth.btservice.ProfileService;
39
40import java.util.ArrayList;
41import java.util.Arrays;
42import java.util.HashMap;
43import java.util.List;
44import java.util.Map;
45import java.util.UUID;
46
47/**
48 * Provides Bluetooth AVRCP Controller profile, as a service in the Bluetooth application.
49 */
50public class AvrcpControllerService extends ProfileService {
51    static final String TAG = "AvrcpControllerService";
52    static final boolean DBG = true;
53    static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
54    /*
55     *  Play State Values from JNI
56     */
57    private static final byte JNI_PLAY_STATUS_STOPPED = 0x00;
58    private static final byte JNI_PLAY_STATUS_PLAYING = 0x01;
59    private static final byte JNI_PLAY_STATUS_PAUSED  = 0x02;
60    private static final byte JNI_PLAY_STATUS_FWD_SEEK = 0x03;
61    private static final byte JNI_PLAY_STATUS_REV_SEEK = 0x04;
62    private static final byte JNI_PLAY_STATUS_ERROR    = -1;
63
64    /*
65     * Browsing Media Item Attribute IDs
66     * This should be kept in sync with BTRC_MEDIA_ATTR_ID_* in bt_rc.h
67     */
68    private static final int JNI_MEDIA_ATTR_ID_INVALID = -1;
69    private static final int JNI_MEDIA_ATTR_ID_TITLE = 0x00000001;
70    private static final int JNI_MEDIA_ATTR_ID_ARTIST = 0x00000002;
71    private static final int JNI_MEDIA_ATTR_ID_ALBUM = 0x00000003;
72    private static final int JNI_MEDIA_ATTR_ID_TRACK_NUM = 0x00000004;
73    private static final int JNI_MEDIA_ATTR_ID_NUM_TRACKS = 0x00000005;
74    private static final int JNI_MEDIA_ATTR_ID_GENRE = 0x00000006;
75    private static final int JNI_MEDIA_ATTR_ID_PLAYING_TIME = 0x00000007;
76
77    /*
78     * Browsing folder types
79     * This should be kept in sync with BTRC_FOLDER_TYPE_* in bt_rc.h
80     */
81    private static final int JNI_FOLDER_TYPE_TITLES = 0x01;
82    private static final int JNI_FOLDER_TYPE_ALBUMS = 0x02;
83    private static final int JNI_FOLDER_TYPE_ARTISTS = 0x03;
84    private static final int JNI_FOLDER_TYPE_GENRES = 0x04;
85    private static final int JNI_FOLDER_TYPE_PLAYLISTS = 0x05;
86    private static final int JNI_FOLDER_TYPE_YEARS = 0x06;
87
88    /*
89     * AVRCP Error types as defined in spec. Also they should be in sync with btrc_status_t.
90     * NOTE: Not all may be defined.
91     */
92    private static final int JNI_AVRC_STS_NO_ERROR = 0x04;
93    private static final int JNI_AVRC_INV_RANGE = 0x0b;
94
95    /**
96     * Intent used to broadcast the change in browse connection state of the AVRCP Controller
97     * profile.
98     *
99     * <p>This intent will have 2 extras:
100     * <ul>
101     *   <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li>
102     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
103     * </ul>
104     *
105     * <p>{@link #EXTRA_STATE} can be any of
106     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
107     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
108     *
109     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
110     * receive.
111     */
112    public static final String ACTION_BROWSE_CONNECTION_STATE_CHANGED =
113        "android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED";
114
115    /**
116     * intent used to broadcast the change in metadata state of playing track on the avrcp
117     * ag.
118     *
119     * <p>this intent will have the two extras:
120     * <ul>
121     *    <li> {@link #extra_metadata} - {@link mediametadata} containing the current metadata.</li>
122     *    <li> {@link #extra_playback} - {@link playbackstate} containing the current playback
123     *    state. </li>
124     * </ul>
125     */
126    public static final String ACTION_TRACK_EVENT =
127        "android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT";
128
129    /**
130     * Intent used to broadcast the change of folder list.
131     *
132     * <p>This intent will have the one extra:
133     * <ul>
134     *    <li> {@link #EXTRA_FOLDER_LIST} - array of {@link MediaBrowser#MediaItem}
135     *    containing the folder listing of currently selected folder.
136     * </ul>
137     */
138    public static final String ACTION_FOLDER_LIST =
139        "android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST";
140
141    public static final String EXTRA_FOLDER_LIST =
142            "android.bluetooth.avrcp-controller.profile.extra.FOLDER_LIST";
143
144    public static final String EXTRA_FOLDER_ID = "com.android.bluetooth.avrcp.EXTRA_FOLDER_ID";
145    public static final String EXTRA_FOLDER_BT_ID =
146        "com.android.bluetooth.avrcp-controller.EXTRA_FOLDER_BT_ID";
147
148    public static final String EXTRA_METADATA =
149            "android.bluetooth.avrcp-controller.profile.extra.METADATA";
150
151    public static final String EXTRA_PLAYBACK =
152            "android.bluetooth.avrcp-controller.profile.extra.PLAYBACK";
153
154    public static final String MEDIA_ITEM_UID_KEY = "media-item-uid-key";
155
156    /*
157     * KeyCoded for Pass Through Commands
158     */
159    public static final int PASS_THRU_CMD_ID_PLAY = 0x44;
160    public static final int PASS_THRU_CMD_ID_PAUSE = 0x46;
161    public static final int PASS_THRU_CMD_ID_VOL_UP = 0x41;
162    public static final int PASS_THRU_CMD_ID_VOL_DOWN = 0x42;
163    public static final int PASS_THRU_CMD_ID_STOP = 0x45;
164    public static final int PASS_THRU_CMD_ID_FF = 0x49;
165    public static final int PASS_THRU_CMD_ID_REWIND = 0x48;
166    public static final int PASS_THRU_CMD_ID_FORWARD = 0x4B;
167    public static final int PASS_THRU_CMD_ID_BACKWARD = 0x4C;
168
169    /* Key State Variables */
170    public static final int KEY_STATE_PRESSED = 0;
171    public static final int KEY_STATE_RELEASED = 1;
172
173    /* Group Navigation Key Codes */
174    public static final int PASS_THRU_CMD_ID_NEXT_GRP = 0x00;
175    public static final int PASS_THRU_CMD_ID_PREV_GRP = 0x01;
176
177    /* Folder navigation directions
178     * This is borrowed from AVRCP 1.6 spec and must be kept with same values
179     */
180    public static final int FOLDER_NAVIGATION_DIRECTION_UP = 0x00;
181    public static final int FOLDER_NAVIGATION_DIRECTION_DOWN = 0x01;
182
183    /* Folder/Media Item scopes.
184     * Keep in sync with AVRCP 1.6 sec. 6.10.1
185     */
186    public static final int BROWSE_SCOPE_PLAYER_LIST = 0x00;
187    public static final int BROWSE_SCOPE_VFS = 0x01;
188    public static final int BROWSE_SCOPE_SEARCH = 0x02;
189    public static final int BROWSE_SCOPE_NOW_PLAYING = 0x03;
190
191    private AvrcpControllerStateMachine mAvrcpCtSm;
192    private static AvrcpControllerService sAvrcpControllerService;
193    // UID size is 8 bytes (AVRCP 1.6 spec)
194    private static final byte[] EMPTY_UID = {0, 0, 0, 0, 0, 0, 0, 0};
195
196    // We only support one device.
197    private BluetoothDevice mConnectedDevice = null;
198    // If browse is supported (only valid if mConnectedDevice != null).
199    private boolean mBrowseConnected = false;
200    // Caches the current browse folder. If this is null then root is the currently browsed folder
201    // (which also has no UID).
202    private String mCurrentBrowseFolderUID = null;
203
204    static {
205        classInitNative();
206    }
207
208    public AvrcpControllerService() {
209        initNative();
210    }
211
212    protected String getName() {
213        return TAG;
214    }
215
216    protected IProfileServiceBinder initBinder() {
217        return new BluetoothAvrcpControllerBinder(this);
218    }
219
220    protected boolean start() {
221        HandlerThread thread = new HandlerThread("BluetoothAvrcpHandler");
222        thread.start();
223        mAvrcpCtSm = new AvrcpControllerStateMachine(this);
224        mAvrcpCtSm.start();
225
226        setAvrcpControllerService(this);
227        return true;
228    }
229
230    protected boolean stop() {
231        if (mAvrcpCtSm != null) {
232            mAvrcpCtSm.doQuit();
233        }
234        return true;
235    }
236
237    //API Methods
238
239    public static synchronized AvrcpControllerService getAvrcpControllerService() {
240        if (sAvrcpControllerService != null && sAvrcpControllerService.isAvailable()) {
241            if (DBG) {
242                Log.d(TAG, "getAvrcpControllerService(): returning "
243                    + sAvrcpControllerService);
244            }
245            return sAvrcpControllerService;
246        }
247        if (DBG) {
248            if (sAvrcpControllerService == null) {
249                Log.d(TAG, "getAvrcpControllerService(): service is NULL");
250            } else if (!(sAvrcpControllerService.isAvailable())) {
251                Log.d(TAG, "getAvrcpControllerService(): service is not available");
252            }
253        }
254        return null;
255    }
256
257    private static synchronized void setAvrcpControllerService(AvrcpControllerService instance) {
258        if (instance != null && instance.isAvailable()) {
259            if (DBG) {
260                Log.d(TAG, "setAvrcpControllerService(): set to: " + sAvrcpControllerService);
261            }
262            sAvrcpControllerService = instance;
263        } else {
264            if (DBG) {
265                if (instance == null) {
266                    Log.d(TAG, "setAvrcpControllerService(): service not available");
267                } else if (!instance.isAvailable()) {
268                    Log.d(TAG, "setAvrcpControllerService(): service is cleaning up");
269                }
270            }
271        }
272    }
273
274    private static synchronized void clearAvrcpControllerService() {
275        sAvrcpControllerService = null;
276    }
277
278    public synchronized List<BluetoothDevice> getConnectedDevices() {
279        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
280        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
281        if (mConnectedDevice != null) {
282            devices.add(mConnectedDevice);
283        }
284        return devices;
285    }
286
287    /**
288     * This function only supports STATE_CONNECTED
289     */
290    public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
291        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
292        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
293        for (int i = 0; i < states.length; i++) {
294            if (states[i] == BluetoothProfile.STATE_CONNECTED && mConnectedDevice != null) {
295                devices.add(mConnectedDevice);
296            }
297        }
298        return devices;
299    }
300
301    public synchronized int getConnectionState(BluetoothDevice device) {
302        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
303        return (mConnectedDevice != null ? BluetoothProfile.STATE_CONNECTED :
304            BluetoothProfile.STATE_DISCONNECTED);
305    }
306
307    public synchronized void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
308        Log.v(TAG, "sendGroupNavigationCmd keyCode: " + keyCode + " keyState: " + keyState);
309        if (device == null) {
310            Log.e(TAG, "sendGroupNavigationCmd device is null");
311        }
312
313        if (!(device.equals(mConnectedDevice))) {
314            Log.e(TAG, " Device does not match " + device + " connected " + mConnectedDevice);
315            return;
316        }
317        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
318        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
319            MESSAGE_SEND_GROUP_NAVIGATION_CMD, keyCode, keyState, device);
320        mAvrcpCtSm.sendMessage(msg);
321    }
322
323    public synchronized void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
324        Log.v(TAG, "sendPassThroughCmd keyCode: " + keyCode + " keyState: " + keyState);
325        if (device == null) {
326            Log.e(TAG, "sendPassThroughCmd Device is null");
327            return;
328        }
329
330        if (!device.equals(mConnectedDevice)) {
331            Log.w(TAG, " Device does not match device " + device + " conn " + mConnectedDevice);
332            return;
333        }
334
335        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
336        Message msg = mAvrcpCtSm
337            .obtainMessage(AvrcpControllerStateMachine.MESSAGE_SEND_PASS_THROUGH_CMD,
338                keyCode, keyState, device);
339        mAvrcpCtSm.sendMessage(msg);
340    }
341
342    public void startAvrcpUpdates() {
343        mAvrcpCtSm.obtainMessage(
344            AvrcpControllerStateMachine.MESSAGE_START_METADATA_BROADCASTS).sendToTarget();
345    }
346
347    public void stopAvrcpUpdates() {
348        mAvrcpCtSm.obtainMessage(
349            AvrcpControllerStateMachine.MESSAGE_STOP_METADATA_BROADCASTS).sendToTarget();
350    }
351
352    public synchronized MediaMetadata getMetaData(BluetoothDevice device) {
353        if (DBG) {
354            Log.d(TAG, "getMetaData");
355        }
356        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
357        if (device == null) {
358            Log.e(TAG, "getMetadata device is null");
359            return null;
360        }
361
362        if (!device.equals(mConnectedDevice)) {
363            return null;
364        }
365        return mAvrcpCtSm.getCurrentMetaData();
366    }
367
368    public PlaybackState getPlaybackState(BluetoothDevice device) {
369        // Get the cached state by default.
370        return getPlaybackState(device, true);
371    }
372
373    // cached can be used to force a getPlaybackState command. Useful for PTS testing.
374    public synchronized PlaybackState getPlaybackState(BluetoothDevice device, boolean cached) {
375        if (DBG) {
376            Log.d(TAG, "getPlayBackState device = " + device);
377        }
378
379        if (device == null) {
380            Log.e(TAG, "getPlaybackState device is null");
381            return null;
382        }
383
384        if (!device.equals(mConnectedDevice)) {
385            Log.e(TAG, "Device " + device + " does not match connected deivce " + mConnectedDevice);
386            return null;
387
388        }
389        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
390        return mAvrcpCtSm.getCurrentPlayBackState(cached);
391    }
392
393    public synchronized BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
394        if (DBG) {
395            Log.d(TAG, "getPlayerApplicationSetting ");
396        }
397
398        if (device == null) {
399            Log.e(TAG, "getPlayerSettings device is null");
400            return null;
401        }
402
403        if (!device.equals(mConnectedDevice)) {
404            Log.e(TAG, "device " + device + " does not match connected device " + mConnectedDevice);
405            return null;
406        }
407
408        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
409
410        /* Do nothing */
411        return null;
412    }
413
414    public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
415        if (DBG) {
416            Log.d(TAG, "getPlayerApplicationSetting");
417        }
418
419        /* Do nothing */
420        return false;
421    }
422
423    /**
424     * Fetches the list of children for the parentID node.
425     *
426     * This function manages the overall tree for browsing structure.
427     *
428     * Arguments:
429     * device - Device to browse content for.
430     * parentMediaId - ID of the parent that we need to browse content for. Since most
431     * of the players are database unware, fetching a root invalidates all the children.
432     * start - number of item to start scanning from
433     * items - number of items to fetch
434     */
435    public synchronized void getChildren(BluetoothDevice device, String parentMediaId, int start, int items) {
436        if (DBG) {
437            Log.d(TAG, "getChildrent device = " + device + " parent " + parentMediaId);
438        }
439
440        if (device == null) {
441            Log.e(TAG, "getChildren device is null");
442            return;
443        }
444
445        if (!device.equals(mConnectedDevice)) {
446            Log.e(TAG, "getChildren device " + device + " does not match " +
447                mConnectedDevice);
448            return;
449        }
450
451        if (!mBrowseConnected) {
452            Log.e(TAG, "getChildren browse not yet connected");
453            return;
454        }
455
456        if (!mAvrcpCtSm.isConnected()) {
457            return;
458        }
459        mAvrcpCtSm.getChildren(parentMediaId, start, items);
460    }
461
462    public synchronized void getNowPlayingList(BluetoothDevice device, String id, int start, int items) {
463        if (DBG) {
464            Log.d(TAG, "getNowPlayingList device = " + device + " start = " + start +
465                "items = " + items);
466        }
467
468        if (device == null) {
469            Log.e(TAG, "getNowPlayingList device is null");
470            return;
471        }
472
473        if (!device.equals(mConnectedDevice)) {
474            Log.e(TAG, "getNowPlayingList device " + device + " does not match " + mConnectedDevice);
475            return;
476        }
477
478        if (!mBrowseConnected) {
479            Log.e(TAG, "getNowPlayingList browse not yet connected");
480        }
481
482        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
483
484        Message msg = mAvrcpCtSm.obtainMessage(
485            AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST, start, items, id);
486        mAvrcpCtSm.sendMessage(msg);
487    }
488
489    public synchronized void getFolderList(BluetoothDevice device, String id, int start, int items) {
490        if (DBG) {
491            Log.d(TAG, "getFolderListing device = " + device + " start = " + start +
492                "items = " + items);
493        }
494
495        if (device == null) {
496            Log.e(TAG, "getFolderListing device is null");
497            return;
498        }
499
500        if (!device.equals(mConnectedDevice)) {
501            Log.e(TAG, "getFolderListing device " + device + " does not match " + mConnectedDevice);
502            return;
503        }
504
505        if (!mBrowseConnected) {
506            Log.e(TAG, "getFolderListing browse not yet connected");
507            return;
508        }
509
510        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
511
512        Message msg = mAvrcpCtSm.obtainMessage(
513            AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start, items, id);
514        mAvrcpCtSm.sendMessage(msg);
515    }
516
517    public synchronized void getPlayerList(BluetoothDevice device, int start, int items) {
518        if (DBG) {
519            Log.d(TAG, "getPlayerList device = " + device + " start = " + start +
520                "items = " + items);
521        }
522
523        if (device == null) {
524            Log.e(TAG, "getPlayerList device is null");
525            return;
526        }
527
528        if (!device.equals(mConnectedDevice)) {
529            Log.e(TAG, "getPlayerList device " + device + " does not match " + mConnectedDevice);
530            return;
531        }
532
533        if (!mBrowseConnected) {
534            Log.e(TAG, "getPlayerList browse not yet connected");
535            return;
536        }
537
538        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
539
540        Message msg = mAvrcpCtSm.obtainMessage(
541            AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items);
542        mAvrcpCtSm.sendMessage(msg);
543    }
544
545    public synchronized void changeFolderPath(
546            BluetoothDevice device, int direction, String uid, String fid) {
547        if (DBG) {
548            Log.d(TAG, "changeFolderPath device = " + device + " direction " +
549                direction + " uid " + uid);
550        }
551
552        if (device == null) {
553            Log.e(TAG, "changeFolderPath device is null");
554            return;
555        }
556
557        if (!device.equals(mConnectedDevice)) {
558            Log.e(TAG, "changeFolderPath device " + device + " does not match " +
559                mConnectedDevice);
560            return;
561        }
562
563        if (!mBrowseConnected) {
564            Log.e(TAG, "changeFolderPath browse not yet connected");
565            return;
566        }
567
568        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
569
570        Bundle b = new Bundle();
571        b.putString(EXTRA_FOLDER_ID, fid);
572        b.putString(EXTRA_FOLDER_BT_ID, uid);
573        Message msg = mAvrcpCtSm.obtainMessage(
574            AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH, direction, 0, b);
575        mAvrcpCtSm.sendMessage(msg);
576    }
577
578    public void setBrowsedPlayer(BluetoothDevice device, int id, String fid) {
579        if (DBG) {
580            Log.d(TAG, "setBrowsedPlayer device = " + device + " id" + id + " fid " + fid);
581        }
582
583        if (device == null) {
584            Log.e(TAG, "setBrowsedPlayer device is null");
585            return;
586        }
587
588
589        if (!mBrowseConnected) {
590            Log.e(TAG, "setBrowsedPlayer browse not yet connected");
591            return;
592        }
593
594        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
595
596        Message msg = mAvrcpCtSm.obtainMessage(
597            AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, id, 0, fid);
598        mAvrcpCtSm.sendMessage(msg);
599    }
600
601    public synchronized void fetchAttrAndPlayItem(BluetoothDevice device, String uid) {
602        if (DBG) {
603            Log.d(TAG, "fetchAttrAndPlayItem device = " + device + " uid " + uid);
604        }
605
606        if (device == null) {
607            Log.e(TAG, "fetchAttrAndPlayItem device is null");
608            return;
609        }
610
611        if (!device.equals(mConnectedDevice)) {
612            Log.e(TAG, "fetchAttrAndPlayItem device " + device + " does not match " +
613                mConnectedDevice);
614            return;
615        }
616
617        if (!mBrowseConnected) {
618            Log.e(TAG, "fetchAttrAndPlayItem browse not yet connected");
619            return;
620        }
621        mAvrcpCtSm.fetchAttrAndPlayItem(uid);
622    }
623
624    //Binder object: Must be static class or memory leak may occur
625    private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
626        implements IProfileServiceBinder {
627
628        private AvrcpControllerService mService;
629
630        private AvrcpControllerService getService() {
631            if (!Utils.checkCaller()) {
632                Log.w(TAG, "AVRCP call not allowed for non-active user");
633                return null;
634            }
635
636            if (mService != null && mService.isAvailable()) {
637                return mService;
638            }
639            return null;
640        }
641
642        BluetoothAvrcpControllerBinder(AvrcpControllerService svc) {
643            mService = svc;
644        }
645
646        public boolean cleanup() {
647            mService = null;
648            return true;
649        }
650
651        @Override
652        public List<BluetoothDevice> getConnectedDevices() {
653            AvrcpControllerService service = getService();
654            if (service == null) {
655                return new ArrayList<BluetoothDevice>(0);
656            }
657            return service.getConnectedDevices();
658        }
659
660        @Override
661        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
662            AvrcpControllerService service = getService();
663            if (service == null) {
664                return new ArrayList<BluetoothDevice>(0);
665            }
666            return service.getDevicesMatchingConnectionStates(states);
667        }
668
669        @Override
670        public int getConnectionState(BluetoothDevice device) {
671            AvrcpControllerService service = getService();
672            if (service == null) {
673                return BluetoothProfile.STATE_DISCONNECTED;
674            }
675
676            if (device == null) {
677              throw new IllegalStateException("Device cannot be null!");
678            }
679
680            return service.getConnectionState(device);
681        }
682
683        @Override
684        public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
685            Log.v(TAG, "Binder Call: sendGroupNavigationCmd");
686            AvrcpControllerService service = getService();
687            if (service == null) {
688                return;
689            }
690
691            if (device == null) {
692              throw new IllegalStateException("Device cannot be null!");
693            }
694
695            service.sendGroupNavigationCmd(device, keyCode, keyState);
696        }
697
698        @Override
699        public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
700            Log.v(TAG, "Binder Call: getPlayerApplicationSetting ");
701            AvrcpControllerService service = getService();
702            if (service == null) {
703                return null;
704            }
705
706            if (device == null) {
707              throw new IllegalStateException("Device cannot be null!");
708            }
709
710            return service.getPlayerSettings(device);
711        }
712
713        @Override
714        public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
715            Log.v(TAG, "Binder Call: setPlayerApplicationSetting ");
716            AvrcpControllerService service = getService();
717            if (service == null) {
718                return false;
719            }
720            return service.setPlayerApplicationSetting(plAppSetting);
721        }
722    }
723
724    // Called by JNI when a passthrough key was received.
725    private void handlePassthroughRsp(int id, int keyState, byte[] address) {
726        Log.d(TAG, "passthrough response received as: key: " + id + " state: " + keyState +
727            "address:" + address);
728    }
729
730    private void handleGroupNavigationRsp(int id, int keyState) {
731        Log.d(TAG, "group navigation response received as: key: " + id + " state: " + keyState);
732    }
733
734    // Called by JNI when a device has connected or disconnected.
735    private synchronized void onConnectionStateChanged(
736            boolean rc_connected, boolean br_connected, byte[] address) {
737        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
738        Log.d(TAG, "onConnectionStateChanged " + rc_connected + " " + br_connected +
739            device + " conn device " + mConnectedDevice);
740        if (device == null) {
741            Log.e(TAG, "onConnectionStateChanged Device is null");
742            return;
743        }
744
745        // Adjust the AVRCP connection state.
746        int oldState = (device.equals(mConnectedDevice) ? BluetoothProfile.STATE_CONNECTED :
747            BluetoothProfile.STATE_DISCONNECTED);
748        int newState = (rc_connected ? BluetoothProfile.STATE_CONNECTED :
749            BluetoothProfile.STATE_DISCONNECTED);
750
751        if (rc_connected && oldState == BluetoothProfile.STATE_DISCONNECTED) {
752            /* AVRCPControllerService supports single connection */
753            if (mConnectedDevice != null) {
754                Log.d(TAG, "A Connection already exists, returning");
755                return;
756            }
757            mConnectedDevice = device;
758            Message msg = mAvrcpCtSm.obtainMessage(
759                AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
760                oldState, device);
761            mAvrcpCtSm.sendMessage(msg);
762        } else if (!rc_connected && oldState == BluetoothProfile.STATE_CONNECTED) {
763            mConnectedDevice = null;
764            Message msg = mAvrcpCtSm.obtainMessage(
765                AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
766                oldState, device);
767            mAvrcpCtSm.sendMessage(msg);
768        }
769
770        // Adjust the browse connection state. If RC is connected we should have already sent the
771        // connection status out.
772        if (rc_connected && br_connected) {
773            mBrowseConnected = true;
774            Message msg = mAvrcpCtSm.obtainMessage(
775               AvrcpControllerStateMachine.MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE);
776            msg.arg1 = 1;
777            msg.obj = device;
778            mAvrcpCtSm.sendMessage(msg);
779        }
780    }
781
782    // Called by JNI to notify Avrcp of features supported by the Remote device.
783    private void getRcFeatures(byte[] address, int features) {
784        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
785        Message msg = mAvrcpCtSm.obtainMessage(
786            AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES, features, 0, device);
787        mAvrcpCtSm.sendMessage(msg);
788    }
789
790    // Called by JNI
791    private void setPlayerAppSettingRsp(byte[] address, byte accepted) {
792              /* Do Nothing. */
793    }
794
795    // Called by JNI when remote wants to receive absolute volume notifications.
796    private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) {
797        Log.d(TAG, "handleRegisterNotificationAbsVol ");
798        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
799        if (device != null && !device.equals(mConnectedDevice)) {
800            Log.e(TAG, "handleRegisterNotificationAbsVol device not found " + address);
801            return;
802        }
803        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
804            MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION, (int) label, 0);
805        mAvrcpCtSm.sendMessage(msg);
806    }
807
808    // Called by JNI when remote wants to set absolute volume.
809    private synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) {
810        Log.d(TAG, "handleSetAbsVolume ");
811        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
812        if (device != null && !device.equals(mConnectedDevice)) {
813            Log.e(TAG, "handleSetAbsVolume device not found " + address);
814            return;
815        }
816        Message msg = mAvrcpCtSm.obtainMessage(
817            AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
818        mAvrcpCtSm.sendMessage(msg);
819    }
820
821    // Called by JNI when a track changes and local AvrcpController is registered for updates.
822    private synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
823        String[] attribVals) {
824        if (DBG) {
825            Log.d(TAG, "onTrackChanged");
826        }
827        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
828        if (device != null && !device.equals(mConnectedDevice)) {
829            Log.e(TAG, "onTrackChanged device not found " + address);
830            return;
831        }
832
833        List<Integer> attrList = new ArrayList<>();
834        for (int attr : attributes) {
835            attrList.add(attr);
836        }
837        List<String> attrValList = Arrays.asList(attribVals);
838        TrackInfo trackInfo = new TrackInfo(attrList, attrValList);
839        if (DBG) {
840            Log.d(TAG, "onTrackChanged " + trackInfo);
841        }
842        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
843            MESSAGE_PROCESS_TRACK_CHANGED, trackInfo);
844        mAvrcpCtSm.sendMessage(msg);
845    }
846
847    // Called by JNI periodically based upon timer to update play position
848    private synchronized void onPlayPositionChanged(byte[] address, int songLen, int currSongPosition) {
849        if (DBG) {
850            Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
851        }
852        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
853        if (device != null && !device.equals(mConnectedDevice)) {
854            Log.e(TAG, "onPlayPositionChanged not found device not found " + address);
855            return;
856        }
857        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
858            MESSAGE_PROCESS_PLAY_POS_CHANGED, songLen, currSongPosition);
859        mAvrcpCtSm.sendMessage(msg);
860    }
861
862    // Called by JNI on changes of play status
863    private synchronized void onPlayStatusChanged(byte[] address, byte playStatus) {
864        if (DBG) {
865            Log.d(TAG, "onPlayStatusChanged " + playStatus);
866        }
867        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
868        if (device != null && !device.equals(mConnectedDevice)) {
869            Log.e(TAG, "onPlayStatusChanged not found device not found " + address);
870            return;
871        }
872        int playbackState = PlaybackState.STATE_NONE;
873        switch (playStatus) {
874            case JNI_PLAY_STATUS_STOPPED:
875                playbackState =  PlaybackState.STATE_STOPPED;
876                break;
877            case JNI_PLAY_STATUS_PLAYING:
878                playbackState =  PlaybackState.STATE_PLAYING;
879                break;
880            case JNI_PLAY_STATUS_PAUSED:
881                playbackState = PlaybackState.STATE_PAUSED;
882                break;
883            case JNI_PLAY_STATUS_FWD_SEEK:
884                playbackState = PlaybackState.STATE_FAST_FORWARDING;
885                break;
886            case JNI_PLAY_STATUS_REV_SEEK:
887                playbackState = PlaybackState.STATE_FAST_FORWARDING;
888                break;
889            default:
890                playbackState = PlaybackState.STATE_NONE;
891        }
892        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
893            MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
894        mAvrcpCtSm.sendMessage(msg);
895    }
896
897    // Called by JNI to report remote Player's capabilities
898    private synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen) {
899        if (DBG) {
900            Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
901        }
902        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
903        if (device != null && !device.equals(mConnectedDevice)) {
904            Log.e(TAG, "handlePlayerAppSetting not found device not found " + address);
905            return;
906        }
907        PlayerApplicationSettings supportedSettings = PlayerApplicationSettings.
908            makeSupportedSettings(playerAttribRsp);
909        /* Do nothing */
910    }
911
912    private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen) {
913        if (DBG) {
914            Log.d(TAG, "onPlayerAppSettingChanged ");
915        }
916        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
917        if (device != null && !device.equals(mConnectedDevice)) {
918            Log.e(TAG, "onPlayerAppSettingChanged not found device not found " + address);
919            return;
920        }
921        PlayerApplicationSettings desiredSettings = PlayerApplicationSettings.
922            makeSettings(playerAttribRsp);
923        /* Do nothing */
924    }
925
926    // Browsing related JNI callbacks.
927    void handleGetFolderItemsRsp(int status, MediaItem[] items) {
928        if (DBG) {
929            Log.d(TAG, "handleGetFolderItemsRsp called with status " + status +
930                " items "  + items.length + " items.");
931        }
932
933        if (status == JNI_AVRC_INV_RANGE) {
934            Log.w(TAG, "Sending out of range message.");
935            // Send a special message since this could be used by state machine
936            // to take as a signal that fetch is finished.
937            Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
938                MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
939            mAvrcpCtSm.sendMessage(msg);
940            return;
941        }
942
943        for (MediaItem item : items) {
944            if (DBG) {
945                Log.d(TAG, "media item: " + item + " uid: " + item.getDescription().getMediaId());
946            }
947        }
948        ArrayList<MediaItem> itemsList = new ArrayList<>();
949        for (MediaItem item : items) {
950            itemsList.add(item);
951        }
952        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
953            MESSAGE_PROCESS_GET_FOLDER_ITEMS, itemsList);
954        mAvrcpCtSm.sendMessage(msg);
955    }
956
957    void handleGetPlayerItemsRsp(AvrcpPlayer[] items) {
958        if (DBG) {
959            Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
960        }
961        for (AvrcpPlayer item : items) {
962            if (DBG) {
963                Log.d(TAG, "bt player item: " + item);
964            }
965        }
966        List<AvrcpPlayer> itemsList = new ArrayList<>();
967        for (AvrcpPlayer p : items) {
968            itemsList.add(p);
969        }
970
971        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
972            MESSAGE_PROCESS_GET_PLAYER_ITEMS, itemsList);
973        mAvrcpCtSm.sendMessage(msg);
974    }
975
976    // JNI Helper functions to convert native objects to java.
977    MediaItem createFromNativeMediaItem(
978            byte[] uid, int type, String name, int[] attrIds, String[] attrVals) {
979        if (DBG) {
980            Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type " + type + " name " +
981                name + " attrids " + attrIds + " attrVals " + attrVals);
982        }
983        MediaDescription.Builder mdb = new MediaDescription.Builder();
984
985        Bundle mdExtra = new Bundle();
986        mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid));
987        mdb.setExtras(mdExtra);
988
989        // Generate a random UUID. We do this since database unaware TGs can send multiple
990        // items with same MEDIA_ITEM_UID_KEY.
991        mdb.setMediaId(UUID.randomUUID().toString());
992
993        // Concise readable name.
994        mdb.setTitle(name);
995
996        // We skip the attributes since we can query them using UID for the item above
997        // Also MediaDescription does not give an easy way to provide this unless we pass
998        // it as an MediaMetadata which is put inside the extras.
999        return new MediaItem(mdb.build(), MediaItem.FLAG_PLAYABLE);
1000    }
1001
1002    MediaItem createFromNativeFolderItem(
1003            byte[] uid, int type, String name, int playable) {
1004        if (DBG) {
1005            Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type +
1006                " name " + name + " playable " + playable);
1007        }
1008        MediaDescription.Builder mdb = new MediaDescription.Builder();
1009
1010        // Covert the byte to a hex string. The coversion can be done back here to a
1011        // byte array when needed.
1012        Bundle mdExtra = new Bundle();
1013        mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid));
1014        mdb.setExtras(mdExtra);
1015
1016        // Generate a random UUID. We do this since database unaware TGs can send multiple
1017        // items with same MEDIA_ITEM_UID_KEY.
1018        mdb.setMediaId(UUID.randomUUID().toString());
1019
1020        // Concise readable name.
1021        mdb.setTitle(name);
1022
1023        return new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE);
1024    }
1025
1026    AvrcpPlayer createFromNativePlayerItem(
1027            int id, String name, byte[] transportFlags, int playStatus, int playerType) {
1028        if (DBG) {
1029            Log.d(TAG, "createFromNativePlayerItem name: " + name + " transportFlags " +
1030                transportFlags + " play status " + playStatus + " player type " +
1031                playerType);
1032        }
1033        AvrcpPlayer player = new AvrcpPlayer(id, name, 0, playStatus, playerType);
1034        return player;
1035    }
1036
1037    private void handleChangeFolderRsp(int count) {
1038        if (DBG) {
1039            Log.d(TAG, "handleChangeFolderRsp count: " + count);
1040        }
1041        Message msg = mAvrcpCtSm.obtainMessage(
1042            AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, count);
1043        mAvrcpCtSm.sendMessage(msg);
1044    }
1045
1046    private void handleSetBrowsedPlayerRsp(int items, int depth) {
1047        if (DBG) {
1048            Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
1049        }
1050        Message msg = mAvrcpCtSm.obtainMessage(
1051            AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth);
1052        mAvrcpCtSm.sendMessage(msg);
1053    }
1054
1055    private void handleSetAddressedPlayerRsp(int status) {
1056        if (DBG) {
1057            Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
1058        }
1059        Message msg = mAvrcpCtSm.obtainMessage(
1060            AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
1061        mAvrcpCtSm.sendMessage(msg);
1062    }
1063
1064    @Override
1065    public void dump(StringBuilder sb) {
1066        super.dump(sb);
1067        mAvrcpCtSm.dump(sb);
1068    }
1069
1070    public static String byteUIDToHexString(byte[] uid) {
1071        StringBuilder sb = new StringBuilder();
1072        for (byte b : uid) {
1073            sb.append(String.format("%02X", b));
1074        }
1075        return sb.toString();
1076    }
1077
1078    public static byte[] hexStringToByteUID(String uidStr) {
1079        if (uidStr == null) {
1080            Log.e(TAG, "Null hex string.");
1081            return EMPTY_UID;
1082        } else if (uidStr.length() % 2 == 1) {
1083            // Odd length strings should not be possible.
1084            Log.e(TAG, "Odd length hex string " + uidStr);
1085            return EMPTY_UID;
1086        }
1087        int len = uidStr.length();
1088        byte[] data = new byte[len / 2];
1089        for (int i = 0; i < len; i += 2) {
1090          data[i / 2] = (byte) ((Character.digit(uidStr.charAt(i), 16) << 4)
1091              + Character.digit(uidStr.charAt(i + 1), 16));
1092        }
1093        return data;
1094    }
1095
1096    private native static void classInitNative();
1097
1098    private native void initNative();
1099
1100    private native void cleanupNative();
1101
1102    native static boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
1103
1104    native static boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
1105        int keyState);
1106
1107    native static void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
1108        byte[] atttibIds, byte[] attribVal);
1109
1110    /* This api is used to send response to SET_ABS_VOL_CMD */
1111    native static void sendAbsVolRspNative(byte[] address, int absVol, int label);
1112
1113    /* This api is used to inform remote for any volume level changes */
1114    native static void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
1115        int label);
1116
1117    /* API used to fetch the playback state */
1118    native static void getPlaybackStateNative(byte[] address);
1119    /* API used to fetch the current now playing list */
1120    native static void getNowPlayingListNative(byte[] address, byte start, byte end);
1121    /* API used to fetch the current folder's listing */
1122    native static void getFolderListNative(byte[] address, byte start, byte end);
1123    /* API used to fetch the listing of players */
1124    native static void getPlayerListNative(byte[] address, byte start, byte end);
1125    /* API used to change the folder */
1126    native static void changeFolderPathNative(byte[] address, byte direction, byte[] uid);
1127    native static void playItemNative(
1128        byte[] address, byte scope, byte[] uid, int uidCounter);
1129    native static void setBrowsedPlayerNative(byte[] address, int playerId);
1130    native static void setAddressedPlayerNative(byte[] address, int playerId);
1131}
1132