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