Home | History | Annotate | Download | only in bluetooth
      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 android.bluetooth;
     18 
     19 import android.annotation.SdkConstant;
     20 import android.annotation.SdkConstant.SdkConstantType;
     21 import android.content.Context;
     22 import android.os.Binder;
     23 import android.os.IBinder;
     24 import android.os.RemoteException;
     25 import android.util.Log;
     26 
     27 import java.util.ArrayList;
     28 import java.util.List;
     29 import java.util.concurrent.Executor;
     30 
     31 /**
     32  * Provides the public APIs to control the Bluetooth HID Device profile.
     33  *
     34  * <p>BluetoothHidDevice is a proxy object for controlling the Bluetooth HID Device Service via IPC.
     35  * Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothHidDevice proxy object.
     36  */
     37 public final class BluetoothHidDevice implements BluetoothProfile {
     38     private static final String TAG = BluetoothHidDevice.class.getSimpleName();
     39 
     40     /**
     41      * Intent used to broadcast the change in connection state of the Input Host profile.
     42      *
     43      * <p>This intent will have 3 extras:
     44      *
     45      * <ul>
     46      *   <li>{@link #EXTRA_STATE} - The current state of the profile.
     47      *   <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
     48      *   <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
     49      * </ul>
     50      *
     51      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
     52      * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link
     53      * #STATE_DISCONNECTING}.
     54      *
     55      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive.
     56      */
     57     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     58     public static final String ACTION_CONNECTION_STATE_CHANGED =
     59             "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED";
     60 
     61     /**
     62      * Constant representing unspecified HID device subclass.
     63      *
     64      * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
     65      *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
     66      */
     67     public static final byte SUBCLASS1_NONE = (byte) 0x00;
     68     /**
     69      * Constant representing keyboard subclass.
     70      *
     71      * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
     72      *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
     73      */
     74     public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40;
     75     /**
     76      * Constant representing mouse subclass.
     77      *
     78      * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
     79      *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
     80      */
     81     public static final byte SUBCLASS1_MOUSE = (byte) 0x80;
     82     /**
     83      * Constant representing combo keyboard and mouse subclass.
     84      *
     85      * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
     86      *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
     87      */
     88     public static final byte SUBCLASS1_COMBO = (byte) 0xC0;
     89 
     90     /**
     91      * Constant representing uncategorized HID device subclass.
     92      *
     93      * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
     94      *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
     95      */
     96     public static final byte SUBCLASS2_UNCATEGORIZED = (byte) 0x00;
     97     /**
     98      * Constant representing joystick subclass.
     99      *
    100      * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
    101      *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
    102      */
    103     public static final byte SUBCLASS2_JOYSTICK = (byte) 0x01;
    104     /**
    105      * Constant representing gamepad subclass.
    106      *
    107      * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
    108      *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
    109      */
    110     public static final byte SUBCLASS2_GAMEPAD = (byte) 0x02;
    111     /**
    112      * Constant representing remote control subclass.
    113      *
    114      * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
    115      *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
    116      */
    117     public static final byte SUBCLASS2_REMOTE_CONTROL = (byte) 0x03;
    118     /**
    119      * Constant representing sensing device subclass.
    120      *
    121      * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
    122      *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
    123      */
    124     public static final byte SUBCLASS2_SENSING_DEVICE = (byte) 0x04;
    125     /**
    126      * Constant representing digitizer tablet subclass.
    127      *
    128      * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
    129      *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
    130      */
    131     public static final byte SUBCLASS2_DIGITIZER_TABLET = (byte) 0x05;
    132     /**
    133      * Constant representing card reader subclass.
    134      *
    135      * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
    136      *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
    137      */
    138     public static final byte SUBCLASS2_CARD_READER = (byte) 0x06;
    139 
    140     /**
    141      * Constant representing HID Input Report type.
    142      *
    143      * @see Callback#onGetReport(BluetoothDevice, byte, byte, int)
    144      * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
    145      * @see Callback#onInterruptData(BluetoothDevice, byte, byte[])
    146      */
    147     public static final byte REPORT_TYPE_INPUT = (byte) 1;
    148     /**
    149      * Constant representing HID Output Report type.
    150      *
    151      * @see Callback#onGetReport(BluetoothDevice, byte, byte, int)
    152      * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
    153      * @see Callback#onInterruptData(BluetoothDevice, byte, byte[])
    154      */
    155     public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
    156     /**
    157      * Constant representing HID Feature Report type.
    158      *
    159      * @see Callback#onGetReport(BluetoothDevice, byte, byte, int)
    160      * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
    161      * @see Callback#onInterruptData(BluetoothDevice, byte, byte[])
    162      */
    163     public static final byte REPORT_TYPE_FEATURE = (byte) 3;
    164 
    165     /**
    166      * Constant representing success response for Set Report.
    167      *
    168      * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
    169      */
    170     public static final byte ERROR_RSP_SUCCESS = (byte) 0;
    171     /**
    172      * Constant representing error response for Set Report due to "not ready".
    173      *
    174      * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
    175      */
    176     public static final byte ERROR_RSP_NOT_READY = (byte) 1;
    177     /**
    178      * Constant representing error response for Set Report due to "invalid report ID".
    179      *
    180      * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
    181      */
    182     public static final byte ERROR_RSP_INVALID_RPT_ID = (byte) 2;
    183     /**
    184      * Constant representing error response for Set Report due to "unsupported request".
    185      *
    186      * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
    187      */
    188     public static final byte ERROR_RSP_UNSUPPORTED_REQ = (byte) 3;
    189     /**
    190      * Constant representing error response for Set Report due to "invalid parameter".
    191      *
    192      * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
    193      */
    194     public static final byte ERROR_RSP_INVALID_PARAM = (byte) 4;
    195     /**
    196      * Constant representing error response for Set Report with unknown reason.
    197      *
    198      * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
    199      */
    200     public static final byte ERROR_RSP_UNKNOWN = (byte) 14;
    201 
    202     /**
    203      * Constant representing boot protocol mode used set by host. Default is always {@link
    204      * #PROTOCOL_REPORT_MODE} unless notified otherwise.
    205      *
    206      * @see Callback#onSetProtocol(BluetoothDevice, byte)
    207      */
    208     public static final byte PROTOCOL_BOOT_MODE = (byte) 0;
    209     /**
    210      * Constant representing report protocol mode used set by host. Default is always {@link
    211      * #PROTOCOL_REPORT_MODE} unless notified otherwise.
    212      *
    213      * @see Callback#onSetProtocol(BluetoothDevice, byte)
    214      */
    215     public static final byte PROTOCOL_REPORT_MODE = (byte) 1;
    216 
    217     /**
    218      * The template class that applications use to call callback functions on events from the HID
    219      * host. Callback functions are wrapped in this class and registered to the Android system
    220      * during app registration.
    221      */
    222     public abstract static class Callback {
    223 
    224         private static final String TAG = "BluetoothHidDevCallback";
    225 
    226         /**
    227          * Callback called when application registration state changes. Usually it's called due to
    228          * either {@link BluetoothHidDevice#registerApp (String, String, String, byte, byte[],
    229          * Executor, Callback)} or {@link BluetoothHidDevice#unregisterApp()} , but can be also
    230          * unsolicited in case e.g. Bluetooth was turned off in which case application is
    231          * unregistered automatically.
    232          *
    233          * @param pluggedDevice {@link BluetoothDevice} object which represents host that currently
    234          *     has Virtual Cable established with device. Only valid when application is registered,
    235          *     can be <code>null</code>.
    236          * @param registered <code>true</code> if application is registered, <code>false</code>
    237          *     otherwise.
    238          */
    239         public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
    240             Log.d(
    241                     TAG,
    242                     "onAppStatusChanged: pluggedDevice="
    243                             + pluggedDevice
    244                             + " registered="
    245                             + registered);
    246         }
    247 
    248         /**
    249          * Callback called when connection state with remote host was changed. Application can
    250          * assume than Virtual Cable is established when called with {@link
    251          * BluetoothProfile#STATE_CONNECTED} <code>state</code>.
    252          *
    253          * @param device {@link BluetoothDevice} object representing host device which connection
    254          *     state was changed.
    255          * @param state Connection state as defined in {@link BluetoothProfile}.
    256          */
    257         public void onConnectionStateChanged(BluetoothDevice device, int state) {
    258             Log.d(TAG, "onConnectionStateChanged: device=" + device + " state=" + state);
    259         }
    260 
    261         /**
    262          * Callback called when GET_REPORT is received from remote host. Should be replied by
    263          * application using {@link BluetoothHidDevice#replyReport(BluetoothDevice, byte, byte,
    264          * byte[])}.
    265          *
    266          * @param type Requested Report Type.
    267          * @param id Requested Report Id, can be 0 if no Report Id are defined in descriptor.
    268          * @param bufferSize Requested buffer size, application shall respond with at least given
    269          *     number of bytes.
    270          */
    271         public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
    272             Log.d(
    273                     TAG,
    274                     "onGetReport: device="
    275                             + device
    276                             + " type="
    277                             + type
    278                             + " id="
    279                             + id
    280                             + " bufferSize="
    281                             + bufferSize);
    282         }
    283 
    284         /**
    285          * Callback called when SET_REPORT is received from remote host. In case received data are
    286          * invalid, application shall respond with {@link
    287          * BluetoothHidDevice#reportError(BluetoothDevice, byte)}.
    288          *
    289          * @param type Report Type.
    290          * @param id Report Id.
    291          * @param data Report data.
    292          */
    293         public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
    294             Log.d(TAG, "onSetReport: device=" + device + " type=" + type + " id=" + id);
    295         }
    296 
    297         /**
    298          * Callback called when SET_PROTOCOL is received from remote host. Application shall use
    299          * this information to send only reports valid for given protocol mode. By default, {@link
    300          * BluetoothHidDevice#PROTOCOL_REPORT_MODE} shall be assumed.
    301          *
    302          * @param protocol Protocol Mode.
    303          */
    304         public void onSetProtocol(BluetoothDevice device, byte protocol) {
    305             Log.d(TAG, "onSetProtocol: device=" + device + " protocol=" + protocol);
    306         }
    307 
    308         /**
    309          * Callback called when report data is received over interrupt channel. Report Type is
    310          * assumed to be {@link BluetoothHidDevice#REPORT_TYPE_OUTPUT}.
    311          *
    312          * @param reportId Report Id.
    313          * @param data Report data.
    314          */
    315         public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
    316             Log.d(TAG, "onInterruptData: device=" + device + " reportId=" + reportId);
    317         }
    318 
    319         /**
    320          * Callback called when Virtual Cable is removed. After this callback is received connection
    321          * will be disconnected automatically.
    322          */
    323         public void onVirtualCableUnplug(BluetoothDevice device) {
    324             Log.d(TAG, "onVirtualCableUnplug: device=" + device);
    325         }
    326     }
    327 
    328     private static class CallbackWrapper extends IBluetoothHidDeviceCallback.Stub {
    329 
    330         private final Executor mExecutor;
    331         private final Callback mCallback;
    332 
    333         CallbackWrapper(Executor executor, Callback callback) {
    334             mExecutor = executor;
    335             mCallback = callback;
    336         }
    337 
    338         @Override
    339         public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
    340             clearCallingIdentity();
    341             mExecutor.execute(() -> mCallback.onAppStatusChanged(pluggedDevice, registered));
    342         }
    343 
    344         @Override
    345         public void onConnectionStateChanged(BluetoothDevice device, int state) {
    346             clearCallingIdentity();
    347             mExecutor.execute(() -> mCallback.onConnectionStateChanged(device, state));
    348         }
    349 
    350         @Override
    351         public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
    352             clearCallingIdentity();
    353             mExecutor.execute(() -> mCallback.onGetReport(device, type, id, bufferSize));
    354         }
    355 
    356         @Override
    357         public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
    358             clearCallingIdentity();
    359             mExecutor.execute(() -> mCallback.onSetReport(device, type, id, data));
    360         }
    361 
    362         @Override
    363         public void onSetProtocol(BluetoothDevice device, byte protocol) {
    364             clearCallingIdentity();
    365             mExecutor.execute(() -> mCallback.onSetProtocol(device, protocol));
    366         }
    367 
    368         @Override
    369         public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
    370             clearCallingIdentity();
    371             mExecutor.execute(() -> mCallback.onInterruptData(device, reportId, data));
    372         }
    373 
    374         @Override
    375         public void onVirtualCableUnplug(BluetoothDevice device) {
    376             clearCallingIdentity();
    377             mExecutor.execute(() -> mCallback.onVirtualCableUnplug(device));
    378         }
    379     }
    380 
    381     private BluetoothAdapter mAdapter;
    382     private final BluetoothProfileConnector<IBluetoothHidDevice> mProfileConnector =
    383             new BluetoothProfileConnector(this, BluetoothProfile.HID_DEVICE,
    384                     "BluetoothHidDevice", IBluetoothHidDevice.class.getName()) {
    385                 @Override
    386                 public IBluetoothHidDevice getServiceInterface(IBinder service) {
    387                     return IBluetoothHidDevice.Stub.asInterface(Binder.allowBlocking(service));
    388                 }
    389     };
    390 
    391     BluetoothHidDevice(Context context, ServiceListener listener) {
    392         mAdapter = BluetoothAdapter.getDefaultAdapter();
    393         mProfileConnector.connect(context, listener);
    394     }
    395 
    396     void close() {
    397         mProfileConnector.disconnect();
    398     }
    399 
    400     private IBluetoothHidDevice getService() {
    401         return mProfileConnector.getService();
    402     }
    403 
    404     /** {@inheritDoc} */
    405     @Override
    406     public List<BluetoothDevice> getConnectedDevices() {
    407         final IBluetoothHidDevice service = getService();
    408         if (service != null) {
    409             try {
    410                 return service.getConnectedDevices();
    411             } catch (RemoteException e) {
    412                 Log.e(TAG, e.toString());
    413             }
    414         } else {
    415             Log.w(TAG, "Proxy not attached to service");
    416         }
    417 
    418         return new ArrayList<>();
    419     }
    420 
    421     /** {@inheritDoc} */
    422     @Override
    423     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    424         final IBluetoothHidDevice service = getService();
    425         if (service != null) {
    426             try {
    427                 return service.getDevicesMatchingConnectionStates(states);
    428             } catch (RemoteException e) {
    429                 Log.e(TAG, e.toString());
    430             }
    431         } else {
    432             Log.w(TAG, "Proxy not attached to service");
    433         }
    434 
    435         return new ArrayList<>();
    436     }
    437 
    438     /** {@inheritDoc} */
    439     @Override
    440     public int getConnectionState(BluetoothDevice device) {
    441         final IBluetoothHidDevice service = getService();
    442         if (service != null) {
    443             try {
    444                 return service.getConnectionState(device);
    445             } catch (RemoteException e) {
    446                 Log.e(TAG, e.toString());
    447             }
    448         } else {
    449             Log.w(TAG, "Proxy not attached to service");
    450         }
    451 
    452         return STATE_DISCONNECTED;
    453     }
    454 
    455     /**
    456      * Registers application to be used for HID device. Connections to HID Device are only possible
    457      * when application is registered. Only one application can be registered at one time. When an
    458      * application is registered, the HID Host service will be disabled until it is unregistered.
    459      * When no longer used, application should be unregistered using {@link #unregisterApp()}. The
    460      * app will be automatically unregistered if it is not foreground. The registration status
    461      * should be tracked by the application by handling callback from Callback#onAppStatusChanged.
    462      * The app registration status is not related to the return value of this method.
    463      *
    464      * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of HID Device SDP record. The HID
    465      *     Device SDP record is required.
    466      * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of Incoming QoS Settings. The
    467      *     Incoming QoS Settings is not required. Use null or default
    468      *     BluetoothHidDeviceAppQosSettings.Builder for default values.
    469      * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of Outgoing QoS Settings. The
    470      *     Outgoing QoS Settings is not required. Use null or default
    471      *     BluetoothHidDeviceAppQosSettings.Builder for default values.
    472      * @param executor {@link Executor} object on which callback will be executed. The Executor
    473      *     object is required.
    474      * @param callback {@link Callback} object to which callback messages will be sent. The Callback
    475      *     object is required.
    476      * @return true if the command is successfully sent; otherwise false.
    477      */
    478     public boolean registerApp(
    479             BluetoothHidDeviceAppSdpSettings sdp,
    480             BluetoothHidDeviceAppQosSettings inQos,
    481             BluetoothHidDeviceAppQosSettings outQos,
    482             Executor executor,
    483             Callback callback) {
    484         boolean result = false;
    485 
    486         if (sdp == null) {
    487             throw new IllegalArgumentException("sdp parameter cannot be null");
    488         }
    489 
    490         if (executor == null) {
    491             throw new IllegalArgumentException("executor parameter cannot be null");
    492         }
    493 
    494         if (callback == null) {
    495             throw new IllegalArgumentException("callback parameter cannot be null");
    496         }
    497 
    498         final IBluetoothHidDevice service = getService();
    499         if (service != null) {
    500             try {
    501                 CallbackWrapper cbw = new CallbackWrapper(executor, callback);
    502                 result = service.registerApp(sdp, inQos, outQos, cbw);
    503             } catch (RemoteException e) {
    504                 Log.e(TAG, e.toString());
    505             }
    506         } else {
    507             Log.w(TAG, "Proxy not attached to service");
    508         }
    509 
    510         return result;
    511     }
    512 
    513     /**
    514      * Unregisters application. Active connection will be disconnected and no new connections will
    515      * be allowed until registered again using {@link #registerApp
    516      * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
    517      * BluetoothHidDeviceAppQosSettings, Executor, Callback)}. The registration status should be
    518      * tracked by the application by handling callback from Callback#onAppStatusChanged. The app
    519      * registration status is not related to the return value of this method.
    520      *
    521      * @return true if the command is successfully sent; otherwise false.
    522      */
    523     public boolean unregisterApp() {
    524         boolean result = false;
    525 
    526         final IBluetoothHidDevice service = getService();
    527         if (service != null) {
    528             try {
    529                 result = service.unregisterApp();
    530             } catch (RemoteException e) {
    531                 Log.e(TAG, e.toString());
    532             }
    533         } else {
    534             Log.w(TAG, "Proxy not attached to service");
    535         }
    536 
    537         return result;
    538     }
    539 
    540     /**
    541      * Sends report to remote host using interrupt channel.
    542      *
    543      * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id are not defined in
    544      *     descriptor.
    545      * @param data Report data, not including Report Id.
    546      * @return true if the command is successfully sent; otherwise false.
    547      */
    548     public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
    549         boolean result = false;
    550 
    551         final IBluetoothHidDevice service = getService();
    552         if (service != null) {
    553             try {
    554                 result = service.sendReport(device, id, data);
    555             } catch (RemoteException e) {
    556                 Log.e(TAG, e.toString());
    557             }
    558         } else {
    559             Log.w(TAG, "Proxy not attached to service");
    560         }
    561 
    562         return result;
    563     }
    564 
    565     /**
    566      * Sends report to remote host as reply for GET_REPORT request from {@link
    567      * Callback#onGetReport(BluetoothDevice, byte, byte, int)}.
    568      *
    569      * @param type Report Type, as in request.
    570      * @param id Report Id, as in request.
    571      * @param data Report data, not including Report Id.
    572      * @return true if the command is successfully sent; otherwise false.
    573      */
    574     public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
    575         boolean result = false;
    576 
    577         final IBluetoothHidDevice service = getService();
    578         if (service != null) {
    579             try {
    580                 result = service.replyReport(device, type, id, data);
    581             } catch (RemoteException e) {
    582                 Log.e(TAG, e.toString());
    583             }
    584         } else {
    585             Log.w(TAG, "Proxy not attached to service");
    586         }
    587 
    588         return result;
    589     }
    590 
    591     /**
    592      * Sends error handshake message as reply for invalid SET_REPORT request from {@link
    593      * Callback#onSetReport(BluetoothDevice, byte, byte, byte[])}.
    594      *
    595      * @param error Error to be sent for SET_REPORT via HANDSHAKE.
    596      * @return true if the command is successfully sent; otherwise false.
    597      */
    598     public boolean reportError(BluetoothDevice device, byte error) {
    599         boolean result = false;
    600 
    601         final IBluetoothHidDevice service = getService();
    602         if (service != null) {
    603             try {
    604                 result = service.reportError(device, error);
    605             } catch (RemoteException e) {
    606                 Log.e(TAG, e.toString());
    607             }
    608         } else {
    609             Log.w(TAG, "Proxy not attached to service");
    610         }
    611 
    612         return result;
    613     }
    614 
    615     /**
    616      * Gets the application name of the current HidDeviceService user.
    617      *
    618      * @return the current user name, or empty string if cannot get the name
    619      * {@hide}
    620      */
    621     public String getUserAppName() {
    622         final IBluetoothHidDevice service = getService();
    623 
    624         if (service != null) {
    625             try {
    626                 return service.getUserAppName();
    627             } catch (RemoteException e) {
    628                 Log.e(TAG, e.toString());
    629             }
    630         } else {
    631             Log.w(TAG, "Proxy not attached to service");
    632         }
    633 
    634         return "";
    635     }
    636 
    637     /**
    638      * Initiates connection to host which is currently paired with this device. If the application
    639      * is not registered, #connect(BluetoothDevice) will fail. The connection state should be
    640      * tracked by the application by handling callback from Callback#onConnectionStateChanged. The
    641      * connection state is not related to the return value of this method.
    642      *
    643      * @return true if the command is successfully sent; otherwise false.
    644      */
    645     public boolean connect(BluetoothDevice device) {
    646         boolean result = false;
    647 
    648         final IBluetoothHidDevice service = getService();
    649         if (service != null) {
    650             try {
    651                 result = service.connect(device);
    652             } catch (RemoteException e) {
    653                 Log.e(TAG, e.toString());
    654             }
    655         } else {
    656             Log.w(TAG, "Proxy not attached to service");
    657         }
    658 
    659         return result;
    660     }
    661 
    662     /**
    663      * Disconnects from currently connected host. The connection state should be tracked by the
    664      * application by handling callback from Callback#onConnectionStateChanged. The connection state
    665      * is not related to the return value of this method.
    666      *
    667      * @return true if the command is successfully sent; otherwise false.
    668      */
    669     public boolean disconnect(BluetoothDevice device) {
    670         boolean result = false;
    671 
    672         final IBluetoothHidDevice service = getService();
    673         if (service != null) {
    674             try {
    675                 result = service.disconnect(device);
    676             } catch (RemoteException e) {
    677                 Log.e(TAG, e.toString());
    678             }
    679         } else {
    680             Log.w(TAG, "Proxy not attached to service");
    681         }
    682 
    683         return result;
    684     }
    685 }
    686