blob: 3bc8544ebf872832ae89e7e52ec4df75b13dfe81 [file] [log] [blame]
Hemant Guptae88fd4b2014-04-18 11:22:45 +05301/*
2 * Copyright (C) 2016 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
17package android.bluetooth;
18
Ivan Podogov0afe1902016-12-23 11:52:21 +000019import android.annotation.SdkConstant;
20import android.annotation.SdkConstant.SdkConstantType;
Hemant Guptae88fd4b2014-04-18 11:22:45 +053021import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.os.IBinder;
26import android.os.RemoteException;
27import android.util.Log;
28
Ivan Podogovdd87cd32016-12-30 14:43:29 +000029import java.util.ArrayList;
Hemant Guptae88fd4b2014-04-18 11:22:45 +053030import java.util.List;
Ivan Podogov191ce9c2018-02-27 17:58:16 +000031import java.util.concurrent.Executor;
Hemant Guptae88fd4b2014-04-18 11:22:45 +053032
33/**
Hansong Zhang53f54122017-12-04 10:31:30 -080034 * Provides the public APIs to control the Bluetooth HID Device profile.
Hansong Zhangfa377b42017-09-27 14:17:20 -070035 *
Hansong Zhang53f54122017-12-04 10:31:30 -080036 * <p>BluetoothHidDevice is a proxy object for controlling the Bluetooth HID Device Service via IPC.
37 * Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothHidDevice proxy object.
Hemant Guptae88fd4b2014-04-18 11:22:45 +053038 */
Hansong Zhangc26c76c2017-10-20 15:55:59 -070039public final class BluetoothHidDevice implements BluetoothProfile {
Hansong Zhangc26c76c2017-10-20 15:55:59 -070040 private static final String TAG = BluetoothHidDevice.class.getSimpleName();
Hemant Guptae88fd4b2014-04-18 11:22:45 +053041
Ivan Podogov0afe1902016-12-23 11:52:21 +000042 /**
Hansong Zhang53f54122017-12-04 10:31:30 -080043 * Intent used to broadcast the change in connection state of the Input Host profile.
Ivan Podogov0afe1902016-12-23 11:52:21 +000044 *
45 * <p>This intent will have 3 extras:
Hansong Zhang53f54122017-12-04 10:31:30 -080046 *
Ivan Podogov0afe1902016-12-23 11:52:21 +000047 * <ul>
Hansong Zhang53f54122017-12-04 10:31:30 -080048 * <li>{@link #EXTRA_STATE} - The current state of the profile.
49 * <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
50 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
Ivan Podogov0afe1902016-12-23 11:52:21 +000051 * </ul>
52 *
Hansong Zhang53f54122017-12-04 10:31:30 -080053 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
54 * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link
55 * #STATE_DISCONNECTING}.
Ivan Podogov0afe1902016-12-23 11:52:21 +000056 *
Hansong Zhang53f54122017-12-04 10:31:30 -080057 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive.
Ivan Podogov0afe1902016-12-23 11:52:21 +000058 */
59 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Hemant Guptae88fd4b2014-04-18 11:22:45 +053060 public static final String ACTION_CONNECTION_STATE_CHANGED =
Hansong Zhangc26c76c2017-10-20 15:55:59 -070061 "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED";
Hemant Guptae88fd4b2014-04-18 11:22:45 +053062
63 /**
Ivan Podogov191ce9c2018-02-27 17:58:16 +000064 * Constant representing unspecified HID device subclass.
Hemant Guptae88fd4b2014-04-18 11:22:45 +053065 *
Hansong Zhang53f54122017-12-04 10:31:30 -080066 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
Ivan Podogov191ce9c2018-02-27 17:58:16 +000067 * BluetoothHidDeviceAppQosSettings, Executor, Callback)
Hemant Guptae88fd4b2014-04-18 11:22:45 +053068 */
69 public static final byte SUBCLASS1_NONE = (byte) 0x00;
Ivan Podogov191ce9c2018-02-27 17:58:16 +000070 /**
71 * Constant representing keyboard subclass.
72 *
73 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
74 * BluetoothHidDeviceAppQosSettings, Executor, Callback)
75 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +053076 public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40;
Ivan Podogov191ce9c2018-02-27 17:58:16 +000077 /**
78 * Constant representing mouse subclass.
79 *
80 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
81 * BluetoothHidDeviceAppQosSettings, Executor, Callback)
82 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +053083 public static final byte SUBCLASS1_MOUSE = (byte) 0x80;
Ivan Podogov191ce9c2018-02-27 17:58:16 +000084 /**
85 * Constant representing combo keyboard and mouse subclass.
86 *
87 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
88 * BluetoothHidDeviceAppQosSettings, Executor, Callback)
89 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +053090 public static final byte SUBCLASS1_COMBO = (byte) 0xC0;
91
Ivan Podogov191ce9c2018-02-27 17:58:16 +000092 /**
93 * Constant representing uncategorized HID device subclass.
94 *
95 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
96 * BluetoothHidDeviceAppQosSettings, Executor, Callback)
97 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +053098 public static final byte SUBCLASS2_UNCATEGORIZED = (byte) 0x00;
Ivan Podogov191ce9c2018-02-27 17:58:16 +000099 /**
100 * Constant representing joystick subclass.
101 *
102 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
103 * BluetoothHidDeviceAppQosSettings, Executor, Callback)
104 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530105 public static final byte SUBCLASS2_JOYSTICK = (byte) 0x01;
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000106 /**
107 * Constant representing gamepad subclass.
108 *
109 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
110 * BluetoothHidDeviceAppQosSettings, Executor, Callback)
111 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530112 public static final byte SUBCLASS2_GAMEPAD = (byte) 0x02;
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000113 /**
114 * Constant representing remote control subclass.
115 *
116 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
117 * BluetoothHidDeviceAppQosSettings, Executor, Callback)
118 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530119 public static final byte SUBCLASS2_REMOTE_CONTROL = (byte) 0x03;
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000120 /**
121 * Constant representing sensing device subclass.
122 *
123 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
124 * BluetoothHidDeviceAppQosSettings, Executor, Callback)
125 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530126 public static final byte SUBCLASS2_SENSING_DEVICE = (byte) 0x04;
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000127 /**
128 * Constant representing digitizer tablet subclass.
129 *
130 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
131 * BluetoothHidDeviceAppQosSettings, Executor, Callback)
132 */
Hansong Zhang9eb138b2017-09-29 09:26:09 -0700133 public static final byte SUBCLASS2_DIGITIZER_TABLET = (byte) 0x05;
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000134 /**
135 * Constant representing card reader subclass.
136 *
137 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
138 * BluetoothHidDeviceAppQosSettings, Executor, Callback)
139 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530140 public static final byte SUBCLASS2_CARD_READER = (byte) 0x06;
141
142 /**
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000143 * Constant representing HID Input Report type.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530144 *
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000145 * @see Callback#onGetReport(BluetoothDevice, byte, byte, int)
146 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
147 * @see Callback#onInterruptData(BluetoothDevice, byte, byte[])
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530148 */
149 public static final byte REPORT_TYPE_INPUT = (byte) 1;
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000150 /**
151 * Constant representing HID Output Report type.
152 *
153 * @see Callback#onGetReport(BluetoothDevice, byte, byte, int)
154 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
155 * @see Callback#onInterruptData(BluetoothDevice, byte, byte[])
156 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530157 public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000158 /**
159 * Constant representing HID Feature Report type.
160 *
161 * @see Callback#onGetReport(BluetoothDevice, byte, byte, int)
162 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
163 * @see Callback#onInterruptData(BluetoothDevice, byte, byte[])
164 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530165 public static final byte REPORT_TYPE_FEATURE = (byte) 3;
166
167 /**
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000168 * Constant representing success response for Set Report.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530169 *
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000170 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530171 */
172 public static final byte ERROR_RSP_SUCCESS = (byte) 0;
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000173 /**
174 * Constant representing error response for Set Report due to "not ready".
175 *
176 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
177 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530178 public static final byte ERROR_RSP_NOT_READY = (byte) 1;
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000179 /**
180 * Constant representing error response for Set Report due to "invalid report ID".
181 *
182 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
183 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530184 public static final byte ERROR_RSP_INVALID_RPT_ID = (byte) 2;
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000185 /**
186 * Constant representing error response for Set Report due to "unsupported request".
187 *
188 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
189 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530190 public static final byte ERROR_RSP_UNSUPPORTED_REQ = (byte) 3;
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000191 /**
192 * Constant representing error response for Set Report due to "invalid parameter".
193 *
194 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
195 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530196 public static final byte ERROR_RSP_INVALID_PARAM = (byte) 4;
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000197 /**
198 * Constant representing error response for Set Report with unknown reason.
199 *
200 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
201 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530202 public static final byte ERROR_RSP_UNKNOWN = (byte) 14;
203
204 /**
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000205 * Constant representing boot protocol mode used set by host. Default is always {@link
Hansong Zhang53f54122017-12-04 10:31:30 -0800206 * #PROTOCOL_REPORT_MODE} unless notified otherwise.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530207 *
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000208 * @see Callback#onSetProtocol(BluetoothDevice, byte)
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530209 */
210 public static final byte PROTOCOL_BOOT_MODE = (byte) 0;
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000211 /**
212 * Constant representing report protocol mode used set by host. Default is always {@link
213 * #PROTOCOL_REPORT_MODE} unless notified otherwise.
214 *
215 * @see Callback#onSetProtocol(BluetoothDevice, byte)
216 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530217 public static final byte PROTOCOL_REPORT_MODE = (byte) 1;
218
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000219 /**
220 * The template class that applications use to call callback functions on events from the HID
221 * host. Callback functions are wrapped in this class and registered to the Android system
222 * during app registration.
223 */
224 public abstract static class Callback {
225
226 private static final String TAG = "BluetoothHidDevCallback";
227
228 /**
229 * Callback called when application registration state changes. Usually it's called due to
230 * either {@link BluetoothHidDevice#registerApp (String, String, String, byte, byte[],
231 * Executor, Callback)} or {@link BluetoothHidDevice#unregisterApp()} , but can be also
232 * unsolicited in case e.g. Bluetooth was turned off in which case application is
233 * unregistered automatically.
234 *
235 * @param pluggedDevice {@link BluetoothDevice} object which represents host that currently
236 * has Virtual Cable established with device. Only valid when application is registered,
237 * can be <code>null</code>.
238 * @param registered <code>true</code> if application is registered, <code>false</code>
239 * otherwise.
240 */
241 public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
242 Log.d(
243 TAG,
244 "onAppStatusChanged: pluggedDevice="
245 + pluggedDevice
246 + " registered="
247 + registered);
248 }
249
250 /**
251 * Callback called when connection state with remote host was changed. Application can
252 * assume than Virtual Cable is established when called with {@link
253 * BluetoothProfile#STATE_CONNECTED} <code>state</code>.
254 *
255 * @param device {@link BluetoothDevice} object representing host device which connection
256 * state was changed.
257 * @param state Connection state as defined in {@link BluetoothProfile}.
258 */
259 public void onConnectionStateChanged(BluetoothDevice device, int state) {
260 Log.d(TAG, "onConnectionStateChanged: device=" + device + " state=" + state);
261 }
262
263 /**
264 * Callback called when GET_REPORT is received from remote host. Should be replied by
265 * application using {@link BluetoothHidDevice#replyReport(BluetoothDevice, byte, byte,
266 * byte[])}.
267 *
268 * @param type Requested Report Type.
269 * @param id Requested Report Id, can be 0 if no Report Id are defined in descriptor.
270 * @param bufferSize Requested buffer size, application shall respond with at least given
271 * number of bytes.
272 */
273 public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
274 Log.d(
275 TAG,
276 "onGetReport: device="
277 + device
278 + " type="
279 + type
280 + " id="
281 + id
282 + " bufferSize="
283 + bufferSize);
284 }
285
286 /**
287 * Callback called when SET_REPORT is received from remote host. In case received data are
288 * invalid, application shall respond with {@link
289 * BluetoothHidDevice#reportError(BluetoothDevice, byte)}.
290 *
291 * @param type Report Type.
292 * @param id Report Id.
293 * @param data Report data.
294 */
295 public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
296 Log.d(TAG, "onSetReport: device=" + device + " type=" + type + " id=" + id);
297 }
298
299 /**
300 * Callback called when SET_PROTOCOL is received from remote host. Application shall use
301 * this information to send only reports valid for given protocol mode. By default, {@link
302 * BluetoothHidDevice#PROTOCOL_REPORT_MODE} shall be assumed.
303 *
304 * @param protocol Protocol Mode.
305 */
306 public void onSetProtocol(BluetoothDevice device, byte protocol) {
307 Log.d(TAG, "onSetProtocol: device=" + device + " protocol=" + protocol);
308 }
309
310 /**
311 * Callback called when report data is received over interrupt channel. Report Type is
312 * assumed to be {@link BluetoothHidDevice#REPORT_TYPE_OUTPUT}.
313 *
314 * @param reportId Report Id.
315 * @param data Report data.
316 */
317 public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
318 Log.d(TAG, "onInterruptData: device=" + device + " reportId=" + reportId);
319 }
320
321 /**
322 * Callback called when Virtual Cable is removed. After this callback is received connection
323 * will be disconnected automatically.
324 */
325 public void onVirtualCableUnplug(BluetoothDevice device) {
326 Log.d(TAG, "onVirtualCableUnplug: device=" + device);
327 }
328 }
329
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530330 private Context mContext;
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530331 private ServiceListener mServiceListener;
Hansong Zhangc26c76c2017-10-20 15:55:59 -0700332 private volatile IBluetoothHidDevice mService;
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530333 private BluetoothAdapter mAdapter;
334
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000335 private static class CallbackWrapper extends IBluetoothHidDeviceCallback.Stub {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530336
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000337 private final Executor mExecutor;
338 private final Callback mCallback;
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530339
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000340 CallbackWrapper(Executor executor, Callback callback) {
341 mExecutor = executor;
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530342 mCallback = callback;
343 }
344
345 @Override
Hansong Zhang7e527b72017-11-30 16:37:05 -0800346 public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000347 clearCallingIdentity();
348 mExecutor.execute(() -> mCallback.onAppStatusChanged(pluggedDevice, registered));
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530349 }
350
351 @Override
352 public void onConnectionStateChanged(BluetoothDevice device, int state) {
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000353 clearCallingIdentity();
354 mExecutor.execute(() -> mCallback.onConnectionStateChanged(device, state));
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530355 }
356
357 @Override
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000358 public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000359 clearCallingIdentity();
360 mExecutor.execute(() -> mCallback.onGetReport(device, type, id, bufferSize));
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530361 }
362
363 @Override
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000364 public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000365 clearCallingIdentity();
366 mExecutor.execute(() -> mCallback.onSetReport(device, type, id, data));
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530367 }
368
369 @Override
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000370 public void onSetProtocol(BluetoothDevice device, byte protocol) {
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000371 clearCallingIdentity();
372 mExecutor.execute(() -> mCallback.onSetProtocol(device, protocol));
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530373 }
374
375 @Override
Hansong Zhang42324272017-12-18 15:18:39 -0800376 public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000377 clearCallingIdentity();
378 mExecutor.execute(() -> mCallback.onInterruptData(device, reportId, data));
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530379 }
380
381 @Override
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000382 public void onVirtualCableUnplug(BluetoothDevice device) {
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000383 clearCallingIdentity();
384 mExecutor.execute(() -> mCallback.onVirtualCableUnplug(device));
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530385 }
386 }
387
Jack He2992cd02017-08-22 21:21:23 -0700388 private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
Jack Hea355e5e2017-08-22 16:06:54 -0700389 new IBluetoothStateChangeCallback.Stub() {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530390
Jack Hea355e5e2017-08-22 16:06:54 -0700391 public void onBluetoothStateChange(boolean up) {
392 Log.d(TAG, "onBluetoothStateChange: up=" + up);
393 synchronized (mConnection) {
Hansong Zhangfa377b42017-09-27 14:17:20 -0700394 if (up) {
Jack Hea355e5e2017-08-22 16:06:54 -0700395 try {
396 if (mService == null) {
397 Log.d(TAG, "Binding HID Device service...");
398 doBind();
399 }
400 } catch (IllegalStateException e) {
Hansong Zhang53f54122017-12-04 10:31:30 -0800401 Log.e(TAG, "onBluetoothStateChange: could not bind to HID Dev "
402 + "service: ", e);
Jack Hea355e5e2017-08-22 16:06:54 -0700403 } catch (SecurityException e) {
Hansong Zhang53f54122017-12-04 10:31:30 -0800404 Log.e(TAG, "onBluetoothStateChange: could not bind to HID Dev "
405 + "service: ", e);
Jack Hea355e5e2017-08-22 16:06:54 -0700406 }
Hansong Zhangfa377b42017-09-27 14:17:20 -0700407 } else {
408 Log.d(TAG, "Unbinding service...");
409 doUnbind();
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530410 }
411 }
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530412 }
Jack Hea355e5e2017-08-22 16:06:54 -0700413 };
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530414
Hansong Zhang53f54122017-12-04 10:31:30 -0800415 private final ServiceConnection mConnection =
416 new ServiceConnection() {
417 public void onServiceConnected(ComponentName className, IBinder service) {
418 Log.d(TAG, "onServiceConnected()");
419 mService = IBluetoothHidDevice.Stub.asInterface(service);
420 if (mServiceListener != null) {
421 mServiceListener.onServiceConnected(
422 BluetoothProfile.HID_DEVICE, BluetoothHidDevice.this);
423 }
424 }
425
426 public void onServiceDisconnected(ComponentName className) {
427 Log.d(TAG, "onServiceDisconnected()");
428 mService = null;
429 if (mServiceListener != null) {
430 mServiceListener.onServiceDisconnected(BluetoothProfile.HID_DEVICE);
431 }
432 }
433 };
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530434
Hansong Zhangc26c76c2017-10-20 15:55:59 -0700435 BluetoothHidDevice(Context context, ServiceListener listener) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530436 mContext = context;
437 mServiceListener = listener;
438 mAdapter = BluetoothAdapter.getDefaultAdapter();
439
440 IBluetoothManager mgr = mAdapter.getBluetoothManager();
441 if (mgr != null) {
442 try {
443 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
444 } catch (RemoteException e) {
445 e.printStackTrace();
446 }
447 }
448
449 doBind();
450 }
451
452 boolean doBind() {
Hansong Zhangc26c76c2017-10-20 15:55:59 -0700453 Intent intent = new Intent(IBluetoothHidDevice.class.getName());
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530454 ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
455 intent.setComponent(comp);
456 if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700457 mContext.getUser())) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530458 Log.e(TAG, "Could not bind to Bluetooth HID Device Service with " + intent);
459 return false;
460 }
461 Log.d(TAG, "Bound to HID Device Service");
462 return true;
463 }
464
Hansong Zhangfa377b42017-09-27 14:17:20 -0700465 void doUnbind() {
Hansong Zhangfa377b42017-09-27 14:17:20 -0700466 if (mService != null) {
467 mService = null;
468 try {
469 mContext.unbindService(mConnection);
470 } catch (IllegalArgumentException e) {
471 Log.e(TAG, "Unable to unbind HidDevService", e);
472 }
473 }
474 }
475
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530476 void close() {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530477 IBluetoothManager mgr = mAdapter.getBluetoothManager();
478 if (mgr != null) {
479 try {
480 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
481 } catch (RemoteException e) {
482 e.printStackTrace();
483 }
484 }
485
486 synchronized (mConnection) {
Hansong Zhangfa377b42017-09-27 14:17:20 -0700487 doUnbind();
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530488 }
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530489 mServiceListener = null;
490 }
491
Hansong Zhang53f54122017-12-04 10:31:30 -0800492 /** {@inheritDoc} */
Jack He2992cd02017-08-22 21:21:23 -0700493 @Override
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530494 public List<BluetoothDevice> getConnectedDevices() {
Hansong Zhangc26c76c2017-10-20 15:55:59 -0700495 final IBluetoothHidDevice service = mService;
Jack He16eeac32017-08-17 12:11:18 -0700496 if (service != null) {
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000497 try {
Jack He16eeac32017-08-17 12:11:18 -0700498 return service.getConnectedDevices();
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000499 } catch (RemoteException e) {
500 Log.e(TAG, e.toString());
501 }
502 } else {
503 Log.w(TAG, "Proxy not attached to service");
504 }
505
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000506 return new ArrayList<>();
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530507 }
508
Hansong Zhang53f54122017-12-04 10:31:30 -0800509 /** {@inheritDoc} */
Jack He2992cd02017-08-22 21:21:23 -0700510 @Override
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530511 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
Hansong Zhangc26c76c2017-10-20 15:55:59 -0700512 final IBluetoothHidDevice service = mService;
Jack He16eeac32017-08-17 12:11:18 -0700513 if (service != null) {
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000514 try {
Jack He16eeac32017-08-17 12:11:18 -0700515 return service.getDevicesMatchingConnectionStates(states);
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000516 } catch (RemoteException e) {
517 Log.e(TAG, e.toString());
518 }
519 } else {
520 Log.w(TAG, "Proxy not attached to service");
521 }
522
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000523 return new ArrayList<>();
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530524 }
525
Hansong Zhang53f54122017-12-04 10:31:30 -0800526 /** {@inheritDoc} */
Jack He2992cd02017-08-22 21:21:23 -0700527 @Override
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530528 public int getConnectionState(BluetoothDevice device) {
Hansong Zhangc26c76c2017-10-20 15:55:59 -0700529 final IBluetoothHidDevice service = mService;
Jack He16eeac32017-08-17 12:11:18 -0700530 if (service != null) {
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000531 try {
Jack He16eeac32017-08-17 12:11:18 -0700532 return service.getConnectionState(device);
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000533 } catch (RemoteException e) {
534 Log.e(TAG, e.toString());
535 }
536 } else {
537 Log.w(TAG, "Proxy not attached to service");
538 }
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530539
540 return STATE_DISCONNECTED;
541 }
542
543 /**
Hansong Zhang53f54122017-12-04 10:31:30 -0800544 * Registers application to be used for HID device. Connections to HID Device are only possible
545 * when application is registered. Only one application can be registered at one time. When an
546 * application is registered, the HID Host service will be disabled until it is unregistered.
547 * When no longer used, application should be unregistered using {@link #unregisterApp()}. The
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000548 * app will be automatically unregistered if it is not foreground. The registration status
549 * should be tracked by the application by handling callback from Callback#onAppStatusChanged.
550 * The app registration status is not related to the return value of this method.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530551 *
Hansong Zhang53f54122017-12-04 10:31:30 -0800552 * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of HID Device SDP record. The HID
553 * Device SDP record is required.
554 * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of Incoming QoS Settings. The
555 * Incoming QoS Settings is not required. Use null or default
556 * BluetoothHidDeviceAppQosSettings.Builder for default values.
557 * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of Outgoing QoS Settings. The
558 * Outgoing QoS Settings is not required. Use null or default
559 * BluetoothHidDeviceAppQosSettings.Builder for default values.
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000560 * @param executor {@link Executor} object on which callback will be executed. The Executor
561 * object is required.
562 * @param callback {@link Callback} object to which callback messages will be sent. The Callback
563 * object is required.
Hansong Zhangceb84db2017-11-08 09:57:12 -0800564 * @return true if the command is successfully sent; otherwise false.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530565 */
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000566 public boolean registerApp(
567 BluetoothHidDeviceAppSdpSettings sdp,
568 BluetoothHidDeviceAppQosSettings inQos,
569 BluetoothHidDeviceAppQosSettings outQos,
570 Executor executor,
571 Callback callback) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530572 boolean result = false;
573
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000574 if (sdp == null) {
575 throw new IllegalArgumentException("sdp parameter cannot be null");
576 }
577
578 if (executor == null) {
579 throw new IllegalArgumentException("executor parameter cannot be null");
580 }
581
582 if (callback == null) {
583 throw new IllegalArgumentException("callback parameter cannot be null");
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530584 }
585
Hansong Zhangc26c76c2017-10-20 15:55:59 -0700586 final IBluetoothHidDevice service = mService;
Jack He16eeac32017-08-17 12:11:18 -0700587 if (service != null) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530588 try {
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000589 CallbackWrapper cbw = new CallbackWrapper(executor, callback);
Hansong Zhang7e527b72017-11-30 16:37:05 -0800590 result = service.registerApp(sdp, inQos, outQos, cbw);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530591 } catch (RemoteException e) {
592 Log.e(TAG, e.toString());
593 }
594 } else {
595 Log.w(TAG, "Proxy not attached to service");
596 }
597
598 return result;
599 }
600
601 /**
Hansong Zhang53f54122017-12-04 10:31:30 -0800602 * Unregisters application. Active connection will be disconnected and no new connections will
603 * be allowed until registered again using {@link #registerApp
Hansong Zhangfa377b42017-09-27 14:17:20 -0700604 * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000605 * BluetoothHidDeviceAppQosSettings, Executor, Callback)}. The registration status should be
606 * tracked by the application by handling callback from Callback#onAppStatusChanged. The app
607 * registration status is not related to the return value of this method.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530608 *
Hansong Zhangceb84db2017-11-08 09:57:12 -0800609 * @return true if the command is successfully sent; otherwise false.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530610 */
Hansong Zhang7e527b72017-11-30 16:37:05 -0800611 public boolean unregisterApp() {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530612 boolean result = false;
613
Hansong Zhangc26c76c2017-10-20 15:55:59 -0700614 final IBluetoothHidDevice service = mService;
Jack He16eeac32017-08-17 12:11:18 -0700615 if (service != null) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530616 try {
Hansong Zhang7e527b72017-11-30 16:37:05 -0800617 result = service.unregisterApp();
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530618 } catch (RemoteException e) {
619 Log.e(TAG, e.toString());
620 }
621 } else {
622 Log.w(TAG, "Proxy not attached to service");
623 }
624
625 return result;
626 }
627
628 /**
629 * Sends report to remote host using interrupt channel.
630 *
Jack Hea355e5e2017-08-22 16:06:54 -0700631 * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id are not defined in
Hansong Zhang53f54122017-12-04 10:31:30 -0800632 * descriptor.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530633 * @param data Report data, not including Report Id.
Hansong Zhangceb84db2017-11-08 09:57:12 -0800634 * @return true if the command is successfully sent; otherwise false.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530635 */
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000636 public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530637 boolean result = false;
638
Hansong Zhangc26c76c2017-10-20 15:55:59 -0700639 final IBluetoothHidDevice service = mService;
Jack He16eeac32017-08-17 12:11:18 -0700640 if (service != null) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530641 try {
Jack He16eeac32017-08-17 12:11:18 -0700642 result = service.sendReport(device, id, data);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530643 } catch (RemoteException e) {
644 Log.e(TAG, e.toString());
645 }
646 } else {
647 Log.w(TAG, "Proxy not attached to service");
648 }
649
650 return result;
651 }
652
653 /**
Hansong Zhang53f54122017-12-04 10:31:30 -0800654 * Sends report to remote host as reply for GET_REPORT request from {@link
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000655 * Callback#onGetReport(BluetoothDevice, byte, byte, int)}.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530656 *
657 * @param type Report Type, as in request.
658 * @param id Report Id, as in request.
659 * @param data Report data, not including Report Id.
Hansong Zhangceb84db2017-11-08 09:57:12 -0800660 * @return true if the command is successfully sent; otherwise false.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530661 */
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000662 public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530663 boolean result = false;
664
Hansong Zhangc26c76c2017-10-20 15:55:59 -0700665 final IBluetoothHidDevice service = mService;
Jack He16eeac32017-08-17 12:11:18 -0700666 if (service != null) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530667 try {
Jack He16eeac32017-08-17 12:11:18 -0700668 result = service.replyReport(device, type, id, data);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530669 } catch (RemoteException e) {
670 Log.e(TAG, e.toString());
671 }
672 } else {
673 Log.w(TAG, "Proxy not attached to service");
674 }
675
676 return result;
677 }
678
679 /**
Hansong Zhang53f54122017-12-04 10:31:30 -0800680 * Sends error handshake message as reply for invalid SET_REPORT request from {@link
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000681 * Callback#onSetReport(BluetoothDevice, byte, byte, byte[])}.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530682 *
683 * @param error Error to be sent for SET_REPORT via HANDSHAKE.
Hansong Zhangceb84db2017-11-08 09:57:12 -0800684 * @return true if the command is successfully sent; otherwise false.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530685 */
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000686 public boolean reportError(BluetoothDevice device, byte error) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530687 boolean result = false;
688
Hansong Zhangc26c76c2017-10-20 15:55:59 -0700689 final IBluetoothHidDevice service = mService;
Jack He16eeac32017-08-17 12:11:18 -0700690 if (service != null) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530691 try {
Jack He16eeac32017-08-17 12:11:18 -0700692 result = service.reportError(device, error);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530693 } catch (RemoteException e) {
694 Log.e(TAG, e.toString());
695 }
696 } else {
697 Log.w(TAG, "Proxy not attached to service");
698 }
699
700 return result;
701 }
702
703 /**
Hansong Zhangbaca8672018-04-03 12:12:53 -0700704 * Gets the application name of the current HidDeviceService user.
705 *
706 * @return the current user name, or empty string if cannot get the name
707 * {@hide}
708 */
709 public String getUserAppName() {
710 final IBluetoothHidDevice service = mService;
711
712 if (service != null) {
713 try {
714 return service.getUserAppName();
715 } catch (RemoteException e) {
716 Log.e(TAG, e.toString());
717 }
718 } else {
719 Log.w(TAG, "Proxy not attached to service");
720 }
721
722 return "";
723 }
724
725 /**
Hansong Zhang53f54122017-12-04 10:31:30 -0800726 * Initiates connection to host which is currently paired with this device. If the application
727 * is not registered, #connect(BluetoothDevice) will fail. The connection state should be
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000728 * tracked by the application by handling callback from Callback#onConnectionStateChanged. The
729 * connection state is not related to the return value of this method.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530730 *
Hansong Zhangceb84db2017-11-08 09:57:12 -0800731 * @return true if the command is successfully sent; otherwise false.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530732 */
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000733 public boolean connect(BluetoothDevice device) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530734 boolean result = false;
735
Hansong Zhangc26c76c2017-10-20 15:55:59 -0700736 final IBluetoothHidDevice service = mService;
Jack He16eeac32017-08-17 12:11:18 -0700737 if (service != null) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530738 try {
Jack He16eeac32017-08-17 12:11:18 -0700739 result = service.connect(device);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530740 } catch (RemoteException e) {
741 Log.e(TAG, e.toString());
742 }
743 } else {
744 Log.w(TAG, "Proxy not attached to service");
745 }
746
747 return result;
748 }
749
750 /**
Hansong Zhang53f54122017-12-04 10:31:30 -0800751 * Disconnects from currently connected host. The connection state should be tracked by the
Ivan Podogov191ce9c2018-02-27 17:58:16 +0000752 * application by handling callback from Callback#onConnectionStateChanged. The connection state
753 * is not related to the return value of this method.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530754 *
Hansong Zhangceb84db2017-11-08 09:57:12 -0800755 * @return true if the command is successfully sent; otherwise false.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530756 */
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000757 public boolean disconnect(BluetoothDevice device) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530758 boolean result = false;
759
Hansong Zhangc26c76c2017-10-20 15:55:59 -0700760 final IBluetoothHidDevice service = mService;
Jack He16eeac32017-08-17 12:11:18 -0700761 if (service != null) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530762 try {
Jack He16eeac32017-08-17 12:11:18 -0700763 result = service.disconnect(device);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530764 } catch (RemoteException e) {
765 Log.e(TAG, e.toString());
766 }
767 } else {
768 Log.w(TAG, "Proxy not attached to service");
769 }
770
771 return result;
772 }
773}