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          */
onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered)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          */
onConnectionStateChanged(BluetoothDevice device, int state)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          */
onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize)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          */
onSetReport(BluetoothDevice device, byte type, byte id, byte[] data)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          */
onSetProtocol(BluetoothDevice device, byte protocol)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          */
onInterruptData(BluetoothDevice device, byte reportId, byte[] data)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          */
onVirtualCableUnplug(BluetoothDevice device)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 
CallbackWrapper(Executor executor, Callback callback)340         CallbackWrapper(Executor executor, Callback callback) {
341             mExecutor = executor;
342             mCallback = callback;
343         }
344 
345         @Override
onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered)346         public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
347             clearCallingIdentity();
348             mExecutor.execute(() -> mCallback.onAppStatusChanged(pluggedDevice, registered));
349         }
350 
351         @Override
onConnectionStateChanged(BluetoothDevice device, int state)352         public void onConnectionStateChanged(BluetoothDevice device, int state) {
353             clearCallingIdentity();
354             mExecutor.execute(() -> mCallback.onConnectionStateChanged(device, state));
355         }
356 
357         @Override
onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize)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
onSetReport(BluetoothDevice device, byte type, byte id, byte[] data)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
onSetProtocol(BluetoothDevice device, byte protocol)370         public void onSetProtocol(BluetoothDevice device, byte protocol) {
371             clearCallingIdentity();
372             mExecutor.execute(() -> mCallback.onSetProtocol(device, protocol));
373         }
374 
375         @Override
onInterruptData(BluetoothDevice device, byte reportId, byte[] data)376         public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
377             clearCallingIdentity();
378             mExecutor.execute(() -> mCallback.onInterruptData(device, reportId, data));
379         }
380 
381         @Override
onVirtualCableUnplug(BluetoothDevice device)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 
BluetoothHidDevice(Context context, ServiceListener listener)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 
doBind()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 
doUnbind()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 
close()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
getConnectedDevices()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
getDevicesMatchingConnectionStates(int[] states)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
getConnectionState(BluetoothDevice device)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      */
registerApp( BluetoothHidDeviceAppSdpSettings sdp, BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos, Executor executor, Callback callback)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      */
unregisterApp()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      */
sendReport(BluetoothDevice device, int id, byte[] data)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      */
replyReport(BluetoothDevice device, byte type, byte id, byte[] data)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      */
reportError(BluetoothDevice device, byte error)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      */
getUserAppName()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      */
connect(BluetoothDevice device)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      */
disconnect(BluetoothDevice device)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