• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.SuppressLint;
27 import android.annotation.SystemApi;
28 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
29 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
30 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
31 import android.content.AttributionSource;
32 import android.content.Context;
33 import android.os.IBinder;
34 import android.os.RemoteException;
35 import android.util.Log;
36 
37 import com.android.modules.utils.SynchronousResultReceiver;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.concurrent.TimeoutException;
42 
43 /**
44  * This class provides the public APIs to control the Bluetooth Input
45  * Device Profile.
46  *
47  * <p>BluetoothHidHost is a proxy object for controlling the Bluetooth
48  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
49  * the BluetoothHidHost proxy object.
50  *
51  * <p>Each method is protected with its appropriate permission.
52  *
53  * @hide
54  */
55 @SystemApi
56 public final class BluetoothHidHost implements BluetoothProfile {
57     private static final String TAG = "BluetoothHidHost";
58     private static final boolean DBG = true;
59     private static final boolean VDBG = false;
60 
61     /**
62      * Intent used to broadcast the change in connection state of the Input
63      * Device profile.
64      *
65      * <p>This intent will have 3 extras:
66      * <ul>
67      * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
68      * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
69      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
70      * </ul>
71      *
72      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
73      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
74      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
75      */
76     @SuppressLint("ActionValue")
77     @RequiresLegacyBluetoothPermission
78     @RequiresBluetoothConnectPermission
79     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
80     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
81     public static final String ACTION_CONNECTION_STATE_CHANGED =
82             "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
83 
84     /**
85      * @hide
86      */
87     @RequiresBluetoothConnectPermission
88     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
89     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
90     public static final String ACTION_PROTOCOL_MODE_CHANGED =
91             "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED";
92 
93     /**
94      * @hide
95      */
96     @RequiresBluetoothConnectPermission
97     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
98     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
99     public static final String ACTION_HANDSHAKE =
100             "android.bluetooth.input.profile.action.HANDSHAKE";
101 
102     /**
103      * @hide
104      */
105     @RequiresBluetoothConnectPermission
106     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
107     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
108     public static final String ACTION_REPORT =
109             "android.bluetooth.input.profile.action.REPORT";
110 
111     /**
112      * @hide
113      */
114     @RequiresBluetoothConnectPermission
115     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
116     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
117     public static final String ACTION_VIRTUAL_UNPLUG_STATUS =
118             "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS";
119 
120     /**
121      * @hide
122      */
123     @RequiresBluetoothConnectPermission
124     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
125     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
126     public static final String ACTION_IDLE_TIME_CHANGED =
127             "android.bluetooth.input.profile.action.IDLE_TIME_CHANGED";
128 
129     /**
130      * Return codes for the connect and disconnect Bluez / Dbus calls.
131      *
132      * @hide
133      */
134     public static final int INPUT_DISCONNECT_FAILED_NOT_CONNECTED = 5000;
135 
136     /**
137      * @hide
138      */
139     public static final int INPUT_CONNECT_FAILED_ALREADY_CONNECTED = 5001;
140 
141     /**
142      * @hide
143      */
144     public static final int INPUT_CONNECT_FAILED_ATTEMPT_FAILED = 5002;
145 
146     /**
147      * @hide
148      */
149     public static final int INPUT_OPERATION_GENERIC_FAILURE = 5003;
150 
151     /**
152      * @hide
153      */
154     public static final int INPUT_OPERATION_SUCCESS = 5004;
155 
156     /**
157      * @hide
158      */
159     public static final int PROTOCOL_REPORT_MODE = 0;
160 
161     /**
162      * @hide
163      */
164     public static final int PROTOCOL_BOOT_MODE = 1;
165 
166     /**
167      * @hide
168      */
169     public static final int PROTOCOL_UNSUPPORTED_MODE = 255;
170 
171     /*  int reportType, int reportType, int bufferSize */
172     /**
173      * @hide
174      */
175     public static final byte REPORT_TYPE_INPUT = 1;
176 
177     /**
178      * @hide
179      */
180     public static final byte REPORT_TYPE_OUTPUT = 2;
181 
182     /**
183      * @hide
184      */
185     public static final byte REPORT_TYPE_FEATURE = 3;
186 
187     /**
188      * @hide
189      */
190     public static final int VIRTUAL_UNPLUG_STATUS_SUCCESS = 0;
191 
192     /**
193      * @hide
194      */
195     public static final int VIRTUAL_UNPLUG_STATUS_FAIL = 1;
196 
197     /**
198      * @hide
199      */
200     public static final String EXTRA_PROTOCOL_MODE =
201             "android.bluetooth.BluetoothHidHost.extra.PROTOCOL_MODE";
202 
203     /**
204      * @hide
205      */
206     public static final String EXTRA_REPORT_TYPE =
207             "android.bluetooth.BluetoothHidHost.extra.REPORT_TYPE";
208 
209     /**
210      * @hide
211      */
212     public static final String EXTRA_REPORT_ID =
213             "android.bluetooth.BluetoothHidHost.extra.REPORT_ID";
214 
215     /**
216      * @hide
217      */
218     public static final String EXTRA_REPORT_BUFFER_SIZE =
219             "android.bluetooth.BluetoothHidHost.extra.REPORT_BUFFER_SIZE";
220 
221     /**
222      * @hide
223      */
224     public static final String EXTRA_REPORT = "android.bluetooth.BluetoothHidHost.extra.REPORT";
225 
226     /**
227      * @hide
228      */
229     public static final String EXTRA_STATUS = "android.bluetooth.BluetoothHidHost.extra.STATUS";
230 
231     /**
232      * @hide
233      */
234     public static final String EXTRA_VIRTUAL_UNPLUG_STATUS =
235             "android.bluetooth.BluetoothHidHost.extra.VIRTUAL_UNPLUG_STATUS";
236 
237     /**
238      * @hide
239      */
240     public static final String EXTRA_IDLE_TIME =
241             "android.bluetooth.BluetoothHidHost.extra.IDLE_TIME";
242 
243     private final BluetoothAdapter mAdapter;
244     private final AttributionSource mAttributionSource;
245     private final BluetoothProfileConnector<IBluetoothHidHost> mProfileConnector =
246             new BluetoothProfileConnector(this, BluetoothProfile.HID_HOST,
247                     "BluetoothHidHost", IBluetoothHidHost.class.getName()) {
248                 @Override
249                 public IBluetoothHidHost getServiceInterface(IBinder service) {
250                     return IBluetoothHidHost.Stub.asInterface(service);
251                 }
252     };
253 
254     /**
255      * Create a BluetoothHidHost proxy object for interacting with the local
256      * Bluetooth Service which handles the InputDevice profile
257      */
BluetoothHidHost(Context context, ServiceListener listener, BluetoothAdapter adapter)258     /* package */ BluetoothHidHost(Context context, ServiceListener listener,
259             BluetoothAdapter adapter) {
260         mAdapter = adapter;
261         mAttributionSource = adapter.getAttributionSource();
262         mProfileConnector.connect(context, listener);
263     }
264 
265     /** @hide */
266     @Override
close()267     public void close() {
268         if (VDBG) log("close()");
269         mProfileConnector.disconnect();
270     }
271 
getService()272     private IBluetoothHidHost getService() {
273         return mProfileConnector.getService();
274     }
275 
276     /**
277      * Initiate connection to a profile of the remote bluetooth device.
278      *
279      * <p> The system supports connection to multiple input devices.
280      *
281      * <p> This API returns false in scenarios like the profile on the
282      * device is already connected or Bluetooth is not turned on.
283      * When this API returns true, it is guaranteed that
284      * connection state intent for the profile will be broadcasted with
285      * the state. Users can get the connection state of the profile
286      * from this intent.
287      *
288      * @param device Remote Bluetooth Device
289      * @return false on immediate error, true otherwise
290      * @hide
291      */
292     @RequiresBluetoothConnectPermission
293     @RequiresPermission(allOf = {
294             android.Manifest.permission.BLUETOOTH_CONNECT,
295             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
296     })
connect(BluetoothDevice device)297     public boolean connect(BluetoothDevice device) {
298         if (DBG) log("connect(" + device + ")");
299         final IBluetoothHidHost service = getService();
300         final boolean defaultValue = false;
301         if (service == null) {
302             Log.w(TAG, "Proxy not attached to service");
303             if (DBG) log(Log.getStackTraceString(new Throwable()));
304         } else if (isEnabled() && isValidDevice(device)) {
305             try {
306                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
307                 service.connect(device, mAttributionSource, recv);
308                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
309             } catch (RemoteException | TimeoutException e) {
310                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
311             }
312         }
313         return defaultValue;
314     }
315 
316     /**
317      * Initiate disconnection from a profile
318      *
319      * <p> This API will return false in scenarios like the profile on the
320      * Bluetooth device is not in connected state etc. When this API returns,
321      * true, it is guaranteed that the connection state change
322      * intent will be broadcasted with the state. Users can get the
323      * disconnection state of the profile from this intent.
324      *
325      * <p> If the disconnection is initiated by a remote device, the state
326      * will transition from {@link #STATE_CONNECTED} to
327      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
328      * host (local) device the state will transition from
329      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
330      * state {@link #STATE_DISCONNECTED}. The transition to
331      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
332      * two scenarios.
333      *
334      * @param device Remote Bluetooth Device
335      * @return false on immediate error, true otherwise
336      * @hide
337      */
338     @RequiresBluetoothConnectPermission
339     @RequiresPermission(allOf = {
340             android.Manifest.permission.BLUETOOTH_CONNECT,
341             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
342     })
disconnect(BluetoothDevice device)343     public boolean disconnect(BluetoothDevice device) {
344         if (DBG) log("disconnect(" + device + ")");
345         final IBluetoothHidHost service = getService();
346         final boolean defaultValue = false;
347         if (service == null) {
348             Log.w(TAG, "Proxy not attached to service");
349             if (DBG) log(Log.getStackTraceString(new Throwable()));
350         } else if (isEnabled() && isValidDevice(device)) {
351             try {
352                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
353                 service.disconnect(device, mAttributionSource, recv);
354                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
355             } catch (RemoteException | TimeoutException e) {
356                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
357             }
358         }
359         return defaultValue;
360     }
361 
362     /**
363      * {@inheritDoc}
364      *
365      * @hide
366      */
367     @SystemApi
368     @Override
369     @RequiresBluetoothConnectPermission
370     @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
getConnectedDevices()371     public @NonNull List<BluetoothDevice> getConnectedDevices() {
372         if (VDBG) log("getConnectedDevices()");
373         final IBluetoothHidHost service = getService();
374         final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
375         if (service == null) {
376             Log.w(TAG, "Proxy not attached to service");
377             if (DBG) log(Log.getStackTraceString(new Throwable()));
378         } else if (isEnabled()) {
379             try {
380                 final SynchronousResultReceiver<List<BluetoothDevice>> recv =
381                         SynchronousResultReceiver.get();
382                 service.getConnectedDevices(mAttributionSource, recv);
383                 return Attributable.setAttributionSource(
384                         recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
385                         mAttributionSource);
386             } catch (RemoteException | TimeoutException e) {
387                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
388             }
389         }
390         return defaultValue;
391     }
392 
393     /**
394      * {@inheritDoc}
395      *
396      * @hide
397      */
398     @Override
399     @RequiresBluetoothConnectPermission
400     @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
getDevicesMatchingConnectionStates(int[] states)401     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
402         if (VDBG) log("getDevicesMatchingStates()");
403         final IBluetoothHidHost service = getService();
404         final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
405         if (service == null) {
406             Log.w(TAG, "Proxy not attached to service");
407             if (DBG) log(Log.getStackTraceString(new Throwable()));
408         } else if (isEnabled()) {
409             try {
410                 final SynchronousResultReceiver<List<BluetoothDevice>> recv =
411                         SynchronousResultReceiver.get();
412                 service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
413                 return Attributable.setAttributionSource(
414                         recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
415                         mAttributionSource);
416             } catch (RemoteException | TimeoutException e) {
417                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
418             }
419         }
420         return defaultValue;
421     }
422 
423     /**
424      * {@inheritDoc}
425      *
426      * @hide
427      */
428     @SystemApi
429     @Override
430     @RequiresBluetoothConnectPermission
431     @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
getConnectionState(@onNull BluetoothDevice device)432     public int getConnectionState(@NonNull BluetoothDevice device) {
433         if (VDBG) log("getState(" + device + ")");
434         if (device == null) {
435             throw new IllegalArgumentException("device must not be null");
436         }
437         final IBluetoothHidHost service = getService();
438         final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
439         if (service == null) {
440             Log.w(TAG, "Proxy not attached to service");
441             if (DBG) log(Log.getStackTraceString(new Throwable()));
442         } else if (isEnabled() && isValidDevice(device)) {
443             try {
444                 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
445                 service.getConnectionState(device, mAttributionSource, recv);
446                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
447             } catch (RemoteException | TimeoutException e) {
448                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
449             }
450         }
451         return defaultValue;
452     }
453 
454     /**
455      * Set priority of the profile
456      *
457      * <p> The device should already be paired.
458      * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF},
459      *
460      * @param device Paired bluetooth device
461      * @param priority
462      * @return true if priority is set, false on error
463      * @hide
464      */
465     @RequiresBluetoothConnectPermission
466     @RequiresPermission(allOf = {
467             android.Manifest.permission.BLUETOOTH_CONNECT,
468             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
469     })
setPriority(BluetoothDevice device, int priority)470     public boolean setPriority(BluetoothDevice device, int priority) {
471         if (DBG) log("setPriority(" + device + ", " + priority + ")");
472         return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
473     }
474 
475     /**
476      * Set connection policy of the profile
477      *
478      * <p> The device should already be paired.
479      * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
480      * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
481      *
482      * @param device Paired bluetooth device
483      * @param connectionPolicy is the connection policy to set to for this profile
484      * @return true if connectionPolicy is set, false on error
485      * @hide
486      */
487     @SystemApi
488     @RequiresBluetoothConnectPermission
489     @RequiresPermission(allOf = {
490             android.Manifest.permission.BLUETOOTH_CONNECT,
491             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
492     })
setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)493     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
494             @ConnectionPolicy int connectionPolicy) {
495         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
496         if (device == null) {
497             throw new IllegalArgumentException("device must not be null");
498         }
499         final IBluetoothHidHost service = getService();
500         final boolean defaultValue = false;
501         if (service == null) {
502             Log.w(TAG, "Proxy not attached to service");
503             if (DBG) log(Log.getStackTraceString(new Throwable()));
504         } else if (isEnabled() && isValidDevice(device)
505                 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
506                     || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
507             try {
508                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
509                 service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
510                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
511             } catch (RemoteException | TimeoutException e) {
512                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
513             }
514         }
515         return defaultValue;
516     }
517 
518     /**
519      * Get the priority of the profile.
520      *
521      * <p> The priority can be any of:
522      * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
523      *
524      * @param device Bluetooth device
525      * @return priority of the device
526      * @hide
527      */
528     @RequiresBluetoothConnectPermission
529     @RequiresPermission(allOf = {
530             android.Manifest.permission.BLUETOOTH_CONNECT,
531             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
532     })
getPriority(BluetoothDevice device)533     public int getPriority(BluetoothDevice device) {
534         if (VDBG) log("getPriority(" + device + ")");
535         return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
536     }
537 
538     /**
539      * Get the connection policy of the profile.
540      *
541      * <p> The connection policy can be any of:
542      * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
543      * {@link #CONNECTION_POLICY_UNKNOWN}
544      *
545      * @param device Bluetooth device
546      * @return connection policy of the device
547      * @hide
548      */
549     @SystemApi
550     @RequiresBluetoothConnectPermission
551     @RequiresPermission(allOf = {
552             android.Manifest.permission.BLUETOOTH_CONNECT,
553             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
554     })
getConnectionPolicy(@onNull BluetoothDevice device)555     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
556         if (VDBG) log("getConnectionPolicy(" + device + ")");
557         if (device == null) {
558             throw new IllegalArgumentException("device must not be null");
559         }
560         final IBluetoothHidHost service = getService();
561         final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
562         if (service == null) {
563             Log.w(TAG, "Proxy not attached to service");
564             if (DBG) log(Log.getStackTraceString(new Throwable()));
565         } else if (isEnabled() && isValidDevice(device)) {
566             try {
567                 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
568                 service.getConnectionPolicy(device, mAttributionSource, recv);
569                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
570             } catch (RemoteException | TimeoutException e) {
571                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
572             }
573         }
574         return defaultValue;
575     }
576 
isEnabled()577     private boolean isEnabled() {
578         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
579     }
580 
isValidDevice(BluetoothDevice device)581     private static boolean isValidDevice(BluetoothDevice device) {
582         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
583     }
584 
585     /**
586      * Initiate virtual unplug for a HID input device.
587      *
588      * @param device Remote Bluetooth Device
589      * @return false on immediate error, true otherwise
590      * @hide
591      */
592     @RequiresLegacyBluetoothAdminPermission
593     @RequiresBluetoothConnectPermission
594     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
virtualUnplug(BluetoothDevice device)595     public boolean virtualUnplug(BluetoothDevice device) {
596         if (DBG) log("virtualUnplug(" + device + ")");
597         final IBluetoothHidHost service = getService();
598         final boolean defaultValue = false;
599         if (service == null) {
600             Log.w(TAG, "Proxy not attached to service");
601             if (DBG) log(Log.getStackTraceString(new Throwable()));
602         } else if (isEnabled() && isValidDevice(device)) {
603             try {
604                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
605                 service.virtualUnplug(device, mAttributionSource, recv);
606                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
607             } catch (RemoteException | TimeoutException e) {
608                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
609             }
610         }
611         return defaultValue;
612     }
613 
614     /**
615      * Send Get_Protocol_Mode command to the connected HID input device.
616      *
617      * @param device Remote Bluetooth Device
618      * @return false on immediate error, true otherwise
619      * @hide
620      */
621     @RequiresLegacyBluetoothAdminPermission
622     @RequiresBluetoothConnectPermission
623     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getProtocolMode(BluetoothDevice device)624     public boolean getProtocolMode(BluetoothDevice device) {
625         if (VDBG) log("getProtocolMode(" + device + ")");
626         final IBluetoothHidHost service = getService();
627         final boolean defaultValue = false;
628         if (service == null) {
629             Log.w(TAG, "Proxy not attached to service");
630             if (DBG) log(Log.getStackTraceString(new Throwable()));
631         } else if (isEnabled() && isValidDevice(device)) {
632             try {
633                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
634                 service.getProtocolMode(device, mAttributionSource, recv);
635                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
636             } catch (RemoteException | TimeoutException e) {
637                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
638             }
639         }
640         return defaultValue;
641     }
642 
643     /**
644      * Send Set_Protocol_Mode command to the connected HID input device.
645      *
646      * @param device Remote Bluetooth Device
647      * @return false on immediate error, true otherwise
648      * @hide
649      */
650     @RequiresLegacyBluetoothAdminPermission
651     @RequiresBluetoothConnectPermission
652     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
setProtocolMode(BluetoothDevice device, int protocolMode)653     public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
654         if (DBG) log("setProtocolMode(" + device + ")");
655         final IBluetoothHidHost service = getService();
656         final boolean defaultValue = false;
657         if (service == null) {
658             Log.w(TAG, "Proxy not attached to service");
659             if (DBG) log(Log.getStackTraceString(new Throwable()));
660         } else if (isEnabled() && isValidDevice(device)) {
661             try {
662                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
663                 service.setProtocolMode(device, protocolMode, mAttributionSource, recv);
664                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
665             } catch (RemoteException | TimeoutException e) {
666                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
667             }
668         }
669         return defaultValue;
670     }
671 
672     /**
673      * Send Get_Report command to the connected HID input device.
674      *
675      * @param device Remote Bluetooth Device
676      * @param reportType Report type
677      * @param reportId Report ID
678      * @param bufferSize Report receiving buffer size
679      * @return false on immediate error, true otherwise
680      * @hide
681      */
682     @RequiresLegacyBluetoothAdminPermission
683     @RequiresBluetoothConnectPermission
684     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize)685     public boolean getReport(BluetoothDevice device, byte reportType, byte reportId,
686             int bufferSize) {
687         if (VDBG) {
688             log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId
689                     + "bufferSize=" + bufferSize);
690         }
691         final IBluetoothHidHost service = getService();
692         final boolean defaultValue = false;
693         if (service == null) {
694             Log.w(TAG, "Proxy not attached to service");
695             if (DBG) log(Log.getStackTraceString(new Throwable()));
696         } else if (isEnabled() && isValidDevice(device)) {
697             try {
698                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
699                 service.getReport(device, reportType, reportId, bufferSize, mAttributionSource,
700                         recv);
701                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
702             } catch (RemoteException | TimeoutException e) {
703                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
704             }
705         }
706         return defaultValue;
707     }
708 
709     /**
710      * Send Set_Report command to the connected HID input device.
711      *
712      * @param device Remote Bluetooth Device
713      * @param reportType Report type
714      * @param report Report receiving buffer size
715      * @return false on immediate error, true otherwise
716      * @hide
717      */
718     @RequiresLegacyBluetoothAdminPermission
719     @RequiresBluetoothConnectPermission
720     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
setReport(BluetoothDevice device, byte reportType, String report)721     public boolean setReport(BluetoothDevice device, byte reportType, String report) {
722         if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
723         final IBluetoothHidHost service = getService();
724         final boolean defaultValue = false;
725         if (service == null) {
726             Log.w(TAG, "Proxy not attached to service");
727             if (DBG) log(Log.getStackTraceString(new Throwable()));
728         } else if (isEnabled() && isValidDevice(device)) {
729             try {
730                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
731                 service.setReport(device, reportType, report, mAttributionSource, recv);
732                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
733             } catch (RemoteException | TimeoutException e) {
734                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
735             }
736         }
737         return defaultValue;
738     }
739 
740     /**
741      * Send Send_Data command to the connected HID input device.
742      *
743      * @param device Remote Bluetooth Device
744      * @param report Report to send
745      * @return false on immediate error, true otherwise
746      * @hide
747      */
748     @RequiresLegacyBluetoothAdminPermission
749     @RequiresBluetoothConnectPermission
750     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
sendData(BluetoothDevice device, String report)751     public boolean sendData(BluetoothDevice device, String report) {
752         if (DBG) log("sendData(" + device + "), report=" + report);
753         final IBluetoothHidHost service = getService();
754         final boolean defaultValue = false;
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() && isValidDevice(device)) {
759             try {
760                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
761                 service.sendData(device, report, mAttributionSource, recv);
762                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
763             } catch (RemoteException | TimeoutException e) {
764                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
765             }
766         }
767         return defaultValue;
768     }
769 
770     /**
771      * Send Get_Idle_Time command to the connected HID input device.
772      *
773      * @param device Remote Bluetooth Device
774      * @return false on immediate error, true otherwise
775      * @hide
776      */
777     @RequiresLegacyBluetoothAdminPermission
778     @RequiresBluetoothConnectPermission
779     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getIdleTime(BluetoothDevice device)780     public boolean getIdleTime(BluetoothDevice device) {
781         if (DBG) log("getIdletime(" + device + ")");
782         final IBluetoothHidHost service = getService();
783         final boolean defaultValue = false;
784         if (service == null) {
785             Log.w(TAG, "Proxy not attached to service");
786             if (DBG) log(Log.getStackTraceString(new Throwable()));
787         } else if (isEnabled() && isValidDevice(device)) {
788             try {
789                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
790                 service.getIdleTime(device, mAttributionSource, recv);
791                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
792             } catch (RemoteException | TimeoutException e) {
793                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
794             }
795         }
796         return defaultValue;
797     }
798 
799     /**
800      * Send Set_Idle_Time command to the connected HID input device.
801      *
802      * @param device Remote Bluetooth Device
803      * @param idleTime Idle time to be set on HID Device
804      * @return false on immediate error, true otherwise
805      * @hide
806      */
807     @RequiresLegacyBluetoothAdminPermission
808     @RequiresBluetoothConnectPermission
809     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
setIdleTime(BluetoothDevice device, byte idleTime)810     public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
811         if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
812         final IBluetoothHidHost service = getService();
813         final boolean defaultValue = false;
814         if (service == null) {
815             Log.w(TAG, "Proxy not attached to service");
816             if (DBG) log(Log.getStackTraceString(new Throwable()));
817         } else if (isEnabled() && isValidDevice(device)) {
818             try {
819                 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
820                 service.setIdleTime(device, idleTime, mAttributionSource, recv);
821                 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
822             } catch (RemoteException | TimeoutException e) {
823                 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
824             }
825         }
826         return defaultValue;
827     }
828 
log(String msg)829     private static void log(String msg) {
830         Log.d(TAG, msg);
831     }
832 }
833