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