AvrcpControllerService.java revision 2610dbb678b0bfb44f4dbef3bf1491a86c670e72
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 boolean getChildren(
436            BluetoothDevice device, String parentMediaId, int start, int items) {
437        if (DBG) {
438            Log.d(TAG, "getChildrent device = " + device + " parent " + parentMediaId);
439        }
440
441        if (device == null) {
442            Log.e(TAG, "getChildren device is null");
443            return false;
444        }
445
446        if (!device.equals(mConnectedDevice)) {
447            Log.e(TAG, "getChildren device " + device + " does not match " +
448                mConnectedDevice);
449            return false;
450        }
451
452        if (!mBrowseConnected) {
453            Log.e(TAG, "getChildren browse not yet connected");
454            return false;
455        }
456
457        if (!mAvrcpCtSm.isConnected()) {
458            return false;
459        }
460        mAvrcpCtSm.getChildren(parentMediaId, start, items);
461        return true;
462    }
463
464    public synchronized boolean getNowPlayingList(
465            BluetoothDevice device, String id, int start, int items) {
466        if (DBG) {
467            Log.d(TAG, "getNowPlayingList device = " + device + " start = " + start +
468                "items = " + items);
469        }
470
471        if (device == null) {
472            Log.e(TAG, "getNowPlayingList device is null");
473            return false;
474        }
475
476        if (!device.equals(mConnectedDevice)) {
477            Log.e(TAG, "getNowPlayingList device " + device + " does not match " +
478                mConnectedDevice);
479            return false;
480        }
481
482        if (!mBrowseConnected) {
483            Log.e(TAG, "getNowPlayingList browse not yet connected");
484            return false;
485        }
486
487        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
488
489        Message msg = mAvrcpCtSm.obtainMessage(
490            AvrcpControllerStateMachine.MESSAGE_GET_NOW_PLAYING_LIST, start, items, id);
491        mAvrcpCtSm.sendMessage(msg);
492        return true;
493    }
494
495    public synchronized boolean getFolderList(
496            BluetoothDevice device, String id, int start, int items) {
497        if (DBG) {
498            Log.d(TAG, "getFolderListing device = " + device + " start = " + start +
499                "items = " + items);
500        }
501
502        if (device == null) {
503            Log.e(TAG, "getFolderListing device is null");
504            return false;
505        }
506
507        if (!device.equals(mConnectedDevice)) {
508            Log.e(TAG, "getFolderListing device " + device + " does not match " + mConnectedDevice);
509            return false;
510        }
511
512        if (!mBrowseConnected) {
513            Log.e(TAG, "getFolderListing browse not yet connected");
514            return false;
515        }
516
517        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
518
519        Message msg = mAvrcpCtSm.obtainMessage(
520            AvrcpControllerStateMachine.MESSAGE_GET_FOLDER_LIST, start, items, id);
521        mAvrcpCtSm.sendMessage(msg);
522        return true;
523    }
524
525    public synchronized boolean getPlayerList(BluetoothDevice device, int start, int items) {
526        if (DBG) {
527            Log.d(TAG, "getPlayerList device = " + device + " start = " + start +
528                "items = " + items);
529        }
530
531        if (device == null) {
532            Log.e(TAG, "getPlayerList device is null");
533            return false;
534        }
535
536        if (!device.equals(mConnectedDevice)) {
537            Log.e(TAG, "getPlayerList device " + device + " does not match " + mConnectedDevice);
538            return false;
539        }
540
541        if (!mBrowseConnected) {
542            Log.e(TAG, "getPlayerList browse not yet connected");
543            return false;
544        }
545
546        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
547
548        Message msg = mAvrcpCtSm.obtainMessage(
549            AvrcpControllerStateMachine.MESSAGE_GET_PLAYER_LIST, start, items);
550        mAvrcpCtSm.sendMessage(msg);
551        return true;
552    }
553
554    public synchronized boolean changeFolderPath(
555            BluetoothDevice device, int direction, String uid, String fid) {
556        if (DBG) {
557            Log.d(TAG, "changeFolderPath device = " + device + " direction " +
558                direction + " uid " + uid);
559        }
560
561        if (device == null) {
562            Log.e(TAG, "changeFolderPath device is null");
563            return false;
564        }
565
566        if (!device.equals(mConnectedDevice)) {
567            Log.e(TAG, "changeFolderPath device " + device + " does not match " +
568                mConnectedDevice);
569            return false;
570        }
571
572        if (!mBrowseConnected) {
573            Log.e(TAG, "changeFolderPath browse not yet connected");
574            return false;
575        }
576
577        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
578
579        Bundle b = new Bundle();
580        b.putString(EXTRA_FOLDER_ID, fid);
581        b.putString(EXTRA_FOLDER_BT_ID, uid);
582        Message msg = mAvrcpCtSm.obtainMessage(
583            AvrcpControllerStateMachine.MESSAGE_CHANGE_FOLDER_PATH, direction, 0, b);
584        mAvrcpCtSm.sendMessage(msg);
585        return true;
586    }
587
588    public synchronized boolean setBrowsedPlayer(BluetoothDevice device, int id, String fid) {
589        if (DBG) {
590            Log.d(TAG, "setBrowsedPlayer device = " + device + " id" + id + " fid " + fid);
591        }
592
593        if (device == null) {
594            Log.e(TAG, "setBrowsedPlayer device is null");
595            return false;
596        }
597
598        if (!device.equals(mConnectedDevice)) {
599            Log.e(TAG, "changeFolderPath device " + device + " does not match " +
600                mConnectedDevice);
601            return false;
602        }
603
604        if (!mBrowseConnected) {
605            Log.e(TAG, "setBrowsedPlayer browse not yet connected");
606            return false;
607        }
608
609        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
610
611        Message msg = mAvrcpCtSm.obtainMessage(
612            AvrcpControllerStateMachine.MESSAGE_SET_BROWSED_PLAYER, id, 0, fid);
613        mAvrcpCtSm.sendMessage(msg);
614        return true;
615    }
616
617    public synchronized void fetchAttrAndPlayItem(BluetoothDevice device, String uid) {
618        if (DBG) {
619            Log.d(TAG, "fetchAttrAndPlayItem device = " + device + " uid " + uid);
620        }
621
622        if (device == null) {
623            Log.e(TAG, "fetchAttrAndPlayItem device is null");
624            return;
625        }
626
627        if (!device.equals(mConnectedDevice)) {
628            Log.e(TAG, "fetchAttrAndPlayItem device " + device + " does not match " +
629                mConnectedDevice);
630            return;
631        }
632
633        if (!mBrowseConnected) {
634            Log.e(TAG, "fetchAttrAndPlayItem browse not yet connected");
635            return;
636        }
637        mAvrcpCtSm.fetchAttrAndPlayItem(uid);
638    }
639
640    //Binder object: Must be static class or memory leak may occur
641    private static class BluetoothAvrcpControllerBinder extends IBluetoothAvrcpController.Stub
642        implements IProfileServiceBinder {
643
644        private AvrcpControllerService mService;
645
646        private AvrcpControllerService getService() {
647            if (!Utils.checkCaller()) {
648                Log.w(TAG, "AVRCP call not allowed for non-active user");
649                return null;
650            }
651
652            if (mService != null && mService.isAvailable()) {
653                return mService;
654            }
655            return null;
656        }
657
658        BluetoothAvrcpControllerBinder(AvrcpControllerService svc) {
659            mService = svc;
660        }
661
662        public boolean cleanup() {
663            mService = null;
664            return true;
665        }
666
667        @Override
668        public List<BluetoothDevice> getConnectedDevices() {
669            AvrcpControllerService service = getService();
670            if (service == null) {
671                return new ArrayList<BluetoothDevice>(0);
672            }
673            return service.getConnectedDevices();
674        }
675
676        @Override
677        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
678            AvrcpControllerService service = getService();
679            if (service == null) {
680                return new ArrayList<BluetoothDevice>(0);
681            }
682            return service.getDevicesMatchingConnectionStates(states);
683        }
684
685        @Override
686        public int getConnectionState(BluetoothDevice device) {
687            AvrcpControllerService service = getService();
688            if (service == null) {
689                return BluetoothProfile.STATE_DISCONNECTED;
690            }
691
692            if (device == null) {
693              throw new IllegalStateException("Device cannot be null!");
694            }
695
696            return service.getConnectionState(device);
697        }
698
699        @Override
700        public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
701            Log.v(TAG, "Binder Call: sendGroupNavigationCmd");
702            AvrcpControllerService service = getService();
703            if (service == null) {
704                return;
705            }
706
707            if (device == null) {
708              throw new IllegalStateException("Device cannot be null!");
709            }
710
711            service.sendGroupNavigationCmd(device, keyCode, keyState);
712        }
713
714        @Override
715        public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
716            Log.v(TAG, "Binder Call: getPlayerApplicationSetting ");
717            AvrcpControllerService service = getService();
718            if (service == null) {
719                return null;
720            }
721
722            if (device == null) {
723              throw new IllegalStateException("Device cannot be null!");
724            }
725
726            return service.getPlayerSettings(device);
727        }
728
729        @Override
730        public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
731            Log.v(TAG, "Binder Call: setPlayerApplicationSetting ");
732            AvrcpControllerService service = getService();
733            if (service == null) {
734                return false;
735            }
736            return service.setPlayerApplicationSetting(plAppSetting);
737        }
738    }
739
740    // Called by JNI when a passthrough key was received.
741    private void handlePassthroughRsp(int id, int keyState, byte[] address) {
742        Log.d(TAG, "passthrough response received as: key: " + id + " state: " + keyState +
743            "address:" + address);
744    }
745
746    private void handleGroupNavigationRsp(int id, int keyState) {
747        Log.d(TAG, "group navigation response received as: key: " + id + " state: " + keyState);
748    }
749
750    // Called by JNI when a device has connected or disconnected.
751    private synchronized void onConnectionStateChanged(
752            boolean rc_connected, boolean br_connected, byte[] address) {
753        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
754        Log.d(TAG, "onConnectionStateChanged " + rc_connected + " " + br_connected +
755            device + " conn device " + mConnectedDevice);
756        if (device == null) {
757            Log.e(TAG, "onConnectionStateChanged Device is null");
758            return;
759        }
760
761        // Adjust the AVRCP connection state.
762        int oldState = (device.equals(mConnectedDevice) ? BluetoothProfile.STATE_CONNECTED :
763            BluetoothProfile.STATE_DISCONNECTED);
764        int newState = (rc_connected ? BluetoothProfile.STATE_CONNECTED :
765            BluetoothProfile.STATE_DISCONNECTED);
766
767        if (rc_connected && oldState == BluetoothProfile.STATE_DISCONNECTED) {
768            /* AVRCPControllerService supports single connection */
769            if (mConnectedDevice != null) {
770                Log.d(TAG, "A Connection already exists, returning");
771                return;
772            }
773            mConnectedDevice = device;
774            Message msg = mAvrcpCtSm.obtainMessage(
775                AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
776                oldState, device);
777            mAvrcpCtSm.sendMessage(msg);
778        } else if (!rc_connected && oldState == BluetoothProfile.STATE_CONNECTED) {
779            mConnectedDevice = null;
780            Message msg = mAvrcpCtSm.obtainMessage(
781                AvrcpControllerStateMachine.MESSAGE_PROCESS_CONNECTION_CHANGE, newState,
782                oldState, device);
783            mAvrcpCtSm.sendMessage(msg);
784        }
785
786        // Adjust the browse connection state. If RC is connected we should have already sent the
787        // connection status out.
788        if (rc_connected && br_connected) {
789            mBrowseConnected = true;
790            Message msg = mAvrcpCtSm.obtainMessage(
791               AvrcpControllerStateMachine.MESSAGE_PROCESS_BROWSE_CONNECTION_CHANGE);
792            msg.arg1 = 1;
793            msg.obj = device;
794            mAvrcpCtSm.sendMessage(msg);
795        }
796    }
797
798    // Called by JNI to notify Avrcp of features supported by the Remote device.
799    private void getRcFeatures(byte[] address, int features) {
800        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
801        Message msg = mAvrcpCtSm.obtainMessage(
802            AvrcpControllerStateMachine.MESSAGE_PROCESS_RC_FEATURES, features, 0, device);
803        mAvrcpCtSm.sendMessage(msg);
804    }
805
806    // Called by JNI
807    private void setPlayerAppSettingRsp(byte[] address, byte accepted) {
808              /* Do Nothing. */
809    }
810
811    // Called by JNI when remote wants to receive absolute volume notifications.
812    private synchronized void handleRegisterNotificationAbsVol(byte[] address, byte label) {
813        Log.d(TAG, "handleRegisterNotificationAbsVol ");
814        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
815        if (device != null && !device.equals(mConnectedDevice)) {
816            Log.e(TAG, "handleRegisterNotificationAbsVol device not found " + address);
817            return;
818        }
819        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
820            MESSAGE_PROCESS_REGISTER_ABS_VOL_NOTIFICATION, (int) label, 0);
821        mAvrcpCtSm.sendMessage(msg);
822    }
823
824    // Called by JNI when remote wants to set absolute volume.
825    private synchronized void handleSetAbsVolume(byte[] address, byte absVol, byte label) {
826        Log.d(TAG, "handleSetAbsVolume ");
827        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
828        if (device != null && !device.equals(mConnectedDevice)) {
829            Log.e(TAG, "handleSetAbsVolume device not found " + address);
830            return;
831        }
832        Message msg = mAvrcpCtSm.obtainMessage(
833            AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ABS_VOL_CMD, absVol, label);
834        mAvrcpCtSm.sendMessage(msg);
835    }
836
837    // Called by JNI when a track changes and local AvrcpController is registered for updates.
838    private synchronized void onTrackChanged(byte[] address, byte numAttributes, int[] attributes,
839        String[] attribVals) {
840        if (DBG) {
841            Log.d(TAG, "onTrackChanged");
842        }
843        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
844        if (device != null && !device.equals(mConnectedDevice)) {
845            Log.e(TAG, "onTrackChanged device not found " + address);
846            return;
847        }
848
849        List<Integer> attrList = new ArrayList<>();
850        for (int attr : attributes) {
851            attrList.add(attr);
852        }
853        List<String> attrValList = Arrays.asList(attribVals);
854        TrackInfo trackInfo = new TrackInfo(attrList, attrValList);
855        if (DBG) {
856            Log.d(TAG, "onTrackChanged " + trackInfo);
857        }
858        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
859            MESSAGE_PROCESS_TRACK_CHANGED, trackInfo);
860        mAvrcpCtSm.sendMessage(msg);
861    }
862
863    // Called by JNI periodically based upon timer to update play position
864    private synchronized void onPlayPositionChanged(byte[] address, int songLen, int currSongPosition) {
865        if (DBG) {
866            Log.d(TAG, "onPlayPositionChanged pos " + currSongPosition);
867        }
868        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
869        if (device != null && !device.equals(mConnectedDevice)) {
870            Log.e(TAG, "onPlayPositionChanged not found device not found " + address);
871            return;
872        }
873        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
874            MESSAGE_PROCESS_PLAY_POS_CHANGED, songLen, currSongPosition);
875        mAvrcpCtSm.sendMessage(msg);
876    }
877
878    // Called by JNI on changes of play status
879    private synchronized void onPlayStatusChanged(byte[] address, byte playStatus) {
880        if (DBG) {
881            Log.d(TAG, "onPlayStatusChanged " + playStatus);
882        }
883        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
884        if (device != null && !device.equals(mConnectedDevice)) {
885            Log.e(TAG, "onPlayStatusChanged not found device not found " + address);
886            return;
887        }
888        int playbackState = PlaybackState.STATE_NONE;
889        switch (playStatus) {
890            case JNI_PLAY_STATUS_STOPPED:
891                playbackState =  PlaybackState.STATE_STOPPED;
892                break;
893            case JNI_PLAY_STATUS_PLAYING:
894                playbackState =  PlaybackState.STATE_PLAYING;
895                break;
896            case JNI_PLAY_STATUS_PAUSED:
897                playbackState = PlaybackState.STATE_PAUSED;
898                break;
899            case JNI_PLAY_STATUS_FWD_SEEK:
900                playbackState = PlaybackState.STATE_FAST_FORWARDING;
901                break;
902            case JNI_PLAY_STATUS_REV_SEEK:
903                playbackState = PlaybackState.STATE_FAST_FORWARDING;
904                break;
905            default:
906                playbackState = PlaybackState.STATE_NONE;
907        }
908        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
909            MESSAGE_PROCESS_PLAY_STATUS_CHANGED, playbackState);
910        mAvrcpCtSm.sendMessage(msg);
911    }
912
913    // Called by JNI to report remote Player's capabilities
914    private synchronized void handlePlayerAppSetting(byte[] address, byte[] playerAttribRsp, int rspLen) {
915        if (DBG) {
916            Log.d(TAG, "handlePlayerAppSetting rspLen = " + rspLen);
917        }
918        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
919        if (device != null && !device.equals(mConnectedDevice)) {
920            Log.e(TAG, "handlePlayerAppSetting not found device not found " + address);
921            return;
922        }
923        PlayerApplicationSettings supportedSettings = PlayerApplicationSettings.
924            makeSupportedSettings(playerAttribRsp);
925        /* Do nothing */
926    }
927
928    private synchronized void onPlayerAppSettingChanged(byte[] address, byte[] playerAttribRsp, int rspLen) {
929        if (DBG) {
930            Log.d(TAG, "onPlayerAppSettingChanged ");
931        }
932        BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
933        if (device != null && !device.equals(mConnectedDevice)) {
934            Log.e(TAG, "onPlayerAppSettingChanged not found device not found " + address);
935            return;
936        }
937        PlayerApplicationSettings desiredSettings = PlayerApplicationSettings.
938            makeSettings(playerAttribRsp);
939        /* Do nothing */
940    }
941
942    // Browsing related JNI callbacks.
943    void handleGetFolderItemsRsp(int status, MediaItem[] items) {
944        if (DBG) {
945            Log.d(TAG, "handleGetFolderItemsRsp called with status " + status +
946                " items "  + items.length + " items.");
947        }
948
949        if (status == JNI_AVRC_INV_RANGE) {
950            Log.w(TAG, "Sending out of range message.");
951            // Send a special message since this could be used by state machine
952            // to take as a signal that fetch is finished.
953            Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
954                MESSAGE_PROCESS_GET_FOLDER_ITEMS_OUT_OF_RANGE);
955            mAvrcpCtSm.sendMessage(msg);
956            return;
957        }
958
959        for (MediaItem item : items) {
960            if (DBG) {
961                Log.d(TAG, "media item: " + item + " uid: " + item.getDescription().getMediaId());
962            }
963        }
964        ArrayList<MediaItem> itemsList = new ArrayList<>();
965        for (MediaItem item : items) {
966            itemsList.add(item);
967        }
968        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
969            MESSAGE_PROCESS_GET_FOLDER_ITEMS, itemsList);
970        mAvrcpCtSm.sendMessage(msg);
971    }
972
973    void handleGetPlayerItemsRsp(AvrcpPlayer[] items) {
974        if (DBG) {
975            Log.d(TAG, "handleGetFolderItemsRsp called with " + items.length + " items.");
976        }
977        for (AvrcpPlayer item : items) {
978            if (DBG) {
979                Log.d(TAG, "bt player item: " + item);
980            }
981        }
982        List<AvrcpPlayer> itemsList = new ArrayList<>();
983        for (AvrcpPlayer p : items) {
984            itemsList.add(p);
985        }
986
987        Message msg = mAvrcpCtSm.obtainMessage(AvrcpControllerStateMachine.
988            MESSAGE_PROCESS_GET_PLAYER_ITEMS, itemsList);
989        mAvrcpCtSm.sendMessage(msg);
990    }
991
992    // JNI Helper functions to convert native objects to java.
993    MediaItem createFromNativeMediaItem(
994            byte[] uid, int type, String name, int[] attrIds, String[] attrVals) {
995        if (DBG) {
996            Log.d(TAG, "createFromNativeMediaItem uid: " + uid + " type " + type + " name " +
997                name + " attrids " + attrIds + " attrVals " + attrVals);
998        }
999        MediaDescription.Builder mdb = new MediaDescription.Builder();
1000
1001        Bundle mdExtra = new Bundle();
1002        mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid));
1003        mdb.setExtras(mdExtra);
1004
1005        // Generate a random UUID. We do this since database unaware TGs can send multiple
1006        // items with same MEDIA_ITEM_UID_KEY.
1007        mdb.setMediaId(UUID.randomUUID().toString());
1008
1009        // Concise readable name.
1010        mdb.setTitle(name);
1011
1012        // We skip the attributes since we can query them using UID for the item above
1013        // Also MediaDescription does not give an easy way to provide this unless we pass
1014        // it as an MediaMetadata which is put inside the extras.
1015        return new MediaItem(mdb.build(), MediaItem.FLAG_PLAYABLE);
1016    }
1017
1018    MediaItem createFromNativeFolderItem(
1019            byte[] uid, int type, String name, int playable) {
1020        if (DBG) {
1021            Log.d(TAG, "createFromNativeFolderItem uid: " + uid + " type " + type +
1022                " name " + name + " playable " + playable);
1023        }
1024        MediaDescription.Builder mdb = new MediaDescription.Builder();
1025
1026        // Covert the byte to a hex string. The coversion can be done back here to a
1027        // byte array when needed.
1028        Bundle mdExtra = new Bundle();
1029        mdExtra.putString(MEDIA_ITEM_UID_KEY, byteUIDToHexString(uid));
1030        mdb.setExtras(mdExtra);
1031
1032        // Generate a random UUID. We do this since database unaware TGs can send multiple
1033        // items with same MEDIA_ITEM_UID_KEY.
1034        mdb.setMediaId(UUID.randomUUID().toString());
1035
1036        // Concise readable name.
1037        mdb.setTitle(name);
1038
1039        return new MediaItem(mdb.build(), MediaItem.FLAG_BROWSABLE);
1040    }
1041
1042    AvrcpPlayer createFromNativePlayerItem(
1043            int id, String name, byte[] transportFlags, int playStatus, int playerType) {
1044        if (DBG) {
1045            Log.d(TAG, "createFromNativePlayerItem name: " + name + " transportFlags " +
1046                transportFlags + " play status " + playStatus + " player type " +
1047                playerType);
1048        }
1049        AvrcpPlayer player = new AvrcpPlayer(id, name, 0, playStatus, playerType);
1050        return player;
1051    }
1052
1053    private void handleChangeFolderRsp(int count) {
1054        if (DBG) {
1055            Log.d(TAG, "handleChangeFolderRsp count: " + count);
1056        }
1057        Message msg = mAvrcpCtSm.obtainMessage(
1058            AvrcpControllerStateMachine.MESSAGE_PROCESS_FOLDER_PATH, count);
1059        mAvrcpCtSm.sendMessage(msg);
1060    }
1061
1062    private void handleSetBrowsedPlayerRsp(int items, int depth) {
1063        if (DBG) {
1064            Log.d(TAG, "handleSetBrowsedPlayerRsp depth: " + depth);
1065        }
1066        Message msg = mAvrcpCtSm.obtainMessage(
1067            AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_BROWSED_PLAYER, items, depth);
1068        mAvrcpCtSm.sendMessage(msg);
1069    }
1070
1071    private void handleSetAddressedPlayerRsp(int status) {
1072        if (DBG) {
1073            Log.d(TAG, "handleSetAddressedPlayerRsp status: " + status);
1074        }
1075        Message msg = mAvrcpCtSm.obtainMessage(
1076            AvrcpControllerStateMachine.MESSAGE_PROCESS_SET_ADDRESSED_PLAYER);
1077        mAvrcpCtSm.sendMessage(msg);
1078    }
1079
1080    @Override
1081    public void dump(StringBuilder sb) {
1082        super.dump(sb);
1083        mAvrcpCtSm.dump(sb);
1084    }
1085
1086    public static String byteUIDToHexString(byte[] uid) {
1087        StringBuilder sb = new StringBuilder();
1088        for (byte b : uid) {
1089            sb.append(String.format("%02X", b));
1090        }
1091        return sb.toString();
1092    }
1093
1094    public static byte[] hexStringToByteUID(String uidStr) {
1095        if (uidStr == null) {
1096            Log.e(TAG, "Null hex string.");
1097            return EMPTY_UID;
1098        } else if (uidStr.length() % 2 == 1) {
1099            // Odd length strings should not be possible.
1100            Log.e(TAG, "Odd length hex string " + uidStr);
1101            return EMPTY_UID;
1102        }
1103        int len = uidStr.length();
1104        byte[] data = new byte[len / 2];
1105        for (int i = 0; i < len; i += 2) {
1106          data[i / 2] = (byte) ((Character.digit(uidStr.charAt(i), 16) << 4)
1107              + Character.digit(uidStr.charAt(i + 1), 16));
1108        }
1109        return data;
1110    }
1111
1112    private native static void classInitNative();
1113
1114    private native void initNative();
1115
1116    private native void cleanupNative();
1117
1118    native static boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);
1119
1120    native static boolean sendGroupNavigationCommandNative(byte[] address, int keyCode,
1121        int keyState);
1122
1123    native static void setPlayerApplicationSettingValuesNative(byte[] address, byte numAttrib,
1124        byte[] atttibIds, byte[] attribVal);
1125
1126    /* This api is used to send response to SET_ABS_VOL_CMD */
1127    native static void sendAbsVolRspNative(byte[] address, int absVol, int label);
1128
1129    /* This api is used to inform remote for any volume level changes */
1130    native static void sendRegisterAbsVolRspNative(byte[] address, byte rspType, int absVol,
1131        int label);
1132
1133    /* API used to fetch the playback state */
1134    native static void getPlaybackStateNative(byte[] address);
1135    /* API used to fetch the current now playing list */
1136    native static void getNowPlayingListNative(byte[] address, byte start, byte end);
1137    /* API used to fetch the current folder's listing */
1138    native static void getFolderListNative(byte[] address, byte start, byte end);
1139    /* API used to fetch the listing of players */
1140    native static void getPlayerListNative(byte[] address, byte start, byte end);
1141    /* API used to change the folder */
1142    native static void changeFolderPathNative(byte[] address, byte direction, byte[] uid);
1143    native static void playItemNative(
1144        byte[] address, byte scope, byte[] uid, int uidCounter);
1145    native static void setBrowsedPlayerNative(byte[] address, int playerId);
1146    native static void setAddressedPlayerNative(byte[] address, int playerId);
1147}
1148