• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered)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          */
onConnectionStateChanged(BluetoothDevice device, int state)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          */
onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize)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          */
onSetReport(BluetoothDevice device, byte type, byte id, byte[] data)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          */
onSetProtocol(BluetoothDevice device, byte protocol)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          */
onInterruptData(BluetoothDevice device, byte reportId, byte[] data)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          */
onVirtualCableUnplug(BluetoothDevice device)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 
CallbackWrapper(Executor executor, Callback callback)333         CallbackWrapper(Executor executor, Callback callback) {
334             mExecutor = executor;
335             mCallback = callback;
336         }
337 
338         @Override
onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered)339         public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
340             clearCallingIdentity();
341             mExecutor.execute(() -> mCallback.onAppStatusChanged(pluggedDevice, registered));
342         }
343 
344         @Override
onConnectionStateChanged(BluetoothDevice device, int state)345         public void onConnectionStateChanged(BluetoothDevice device, int state) {
346             clearCallingIdentity();
347             mExecutor.execute(() -> mCallback.onConnectionStateChanged(device, state));
348         }
349 
350         @Override
onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize)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
onSetReport(BluetoothDevice device, byte type, byte id, byte[] data)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
onSetProtocol(BluetoothDevice device, byte protocol)363         public void onSetProtocol(BluetoothDevice device, byte protocol) {
364             clearCallingIdentity();
365             mExecutor.execute(() -> mCallback.onSetProtocol(device, protocol));
366         }
367 
368         @Override
onInterruptData(BluetoothDevice device, byte reportId, byte[] data)369         public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
370             clearCallingIdentity();
371             mExecutor.execute(() -> mCallback.onInterruptData(device, reportId, data));
372         }
373 
374         @Override
onVirtualCableUnplug(BluetoothDevice device)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 
BluetoothHidDevice(Context context, ServiceListener listener)391     BluetoothHidDevice(Context context, ServiceListener listener) {
392         mAdapter = BluetoothAdapter.getDefaultAdapter();
393         mProfileConnector.connect(context, listener);
394     }
395 
close()396     void close() {
397         mProfileConnector.disconnect();
398     }
399 
getService()400     private IBluetoothHidDevice getService() {
401         return mProfileConnector.getService();
402     }
403 
404     /** {@inheritDoc} */
405     @Override
getConnectedDevices()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
getDevicesMatchingConnectionStates(int[] states)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
getConnectionState(BluetoothDevice device)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      */
registerApp( BluetoothHidDeviceAppSdpSettings sdp, BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos, Executor executor, Callback callback)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      */
unregisterApp()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      */
sendReport(BluetoothDevice device, int id, byte[] data)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      */
replyReport(BluetoothDevice device, byte type, byte id, byte[] data)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      */
reportError(BluetoothDevice device, byte error)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      */
getUserAppName()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      */
connect(BluetoothDevice device)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      */
disconnect(BluetoothDevice device)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