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