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