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