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