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