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