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