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