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