blob: 68d105f1155d81c6437906055f01d3ff0a27a51f [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
29import java.util.Arrays;
Ivan Podogovdd87cd32016-12-30 14:43:29 +000030import java.util.ArrayList;
Hemant Guptae88fd4b2014-04-18 11:22:45 +053031import java.util.List;
32
33/**
34 * @hide
35 */
Ivan Podogov0afe1902016-12-23 11:52:21 +000036public final class BluetoothInputHost implements BluetoothProfile {
Hemant Guptae88fd4b2014-04-18 11:22:45 +053037
Ivan Podogov0afe1902016-12-23 11:52:21 +000038 private static final String TAG = BluetoothInputHost.class.getSimpleName();
Hemant Guptae88fd4b2014-04-18 11:22:45 +053039
Ivan Podogov0afe1902016-12-23 11:52:21 +000040 /**
41 * Intent used to broadcast the change in connection state of the Input
42 * Host profile.
43 *
44 * <p>This intent will have 3 extras:
45 * <ul>
46 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
47 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
48 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
49 * </ul>
50 *
51 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
52 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
53 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
54 *
55 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
56 * receive.
57 */
58 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
Hemant Guptae88fd4b2014-04-18 11:22:45 +053059 public static final String ACTION_CONNECTION_STATE_CHANGED =
Ivan Podogov0afe1902016-12-23 11:52:21 +000060 "android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED";
Hemant Guptae88fd4b2014-04-18 11:22:45 +053061
62 /**
63 * Constants representing device subclass.
64 *
65 * @see #registerApp(String, String, String, byte, byte[],
66 * BluetoothHidDeviceCallback)
67 */
68 public static final byte SUBCLASS1_NONE = (byte) 0x00;
69 public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40;
70 public static final byte SUBCLASS1_MOUSE = (byte) 0x80;
71 public static final byte SUBCLASS1_COMBO = (byte) 0xC0;
72
73 public static final byte SUBCLASS2_UNCATEGORIZED = (byte) 0x00;
74 public static final byte SUBCLASS2_JOYSTICK = (byte) 0x01;
75 public static final byte SUBCLASS2_GAMEPAD = (byte) 0x02;
76 public static final byte SUBCLASS2_REMOTE_CONTROL = (byte) 0x03;
77 public static final byte SUBCLASS2_SENSING_DEVICE = (byte) 0x04;
78 public static final byte SUBCLASS2_DIGITIZER_TABLED = (byte) 0x05;
79 public static final byte SUBCLASS2_CARD_READER = (byte) 0x06;
80
81 /**
82 * Constants representing report types.
83 *
84 * @see BluetoothHidDeviceCallback#onGetReport(byte, byte, int)
85 * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
86 * @see BluetoothHidDeviceCallback#onIntrData(byte, byte[])
87 */
88 public static final byte REPORT_TYPE_INPUT = (byte) 1;
89 public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
90 public static final byte REPORT_TYPE_FEATURE = (byte) 3;
91
92 /**
93 * Constants representing error response for Set Report.
94 *
95 * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
96 */
97 public static final byte ERROR_RSP_SUCCESS = (byte) 0;
98 public static final byte ERROR_RSP_NOT_READY = (byte) 1;
99 public static final byte ERROR_RSP_INVALID_RPT_ID = (byte) 2;
100 public static final byte ERROR_RSP_UNSUPPORTED_REQ = (byte) 3;
101 public static final byte ERROR_RSP_INVALID_PARAM = (byte) 4;
102 public static final byte ERROR_RSP_UNKNOWN = (byte) 14;
103
104 /**
105 * Constants representing protocol mode used set by host. Default is always
106 * {@link #PROTOCOL_REPORT_MODE} unless notified otherwise.
107 *
108 * @see BluetoothHidDeviceCallback#onSetProtocol(byte)
109 */
110 public static final byte PROTOCOL_BOOT_MODE = (byte) 0;
111 public static final byte PROTOCOL_REPORT_MODE = (byte) 1;
112
113 private Context mContext;
114
115 private ServiceListener mServiceListener;
116
Ivan Podogov0afe1902016-12-23 11:52:21 +0000117 private IBluetoothInputHost mService;
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530118
119 private BluetoothAdapter mAdapter;
120
121 private static class BluetoothHidDeviceCallbackWrapper extends IBluetoothHidDeviceCallback.Stub {
122
123 private BluetoothHidDeviceCallback mCallback;
124
125 public BluetoothHidDeviceCallbackWrapper(BluetoothHidDeviceCallback callback) {
126 mCallback = callback;
127 }
128
129 @Override
130 public void onAppStatusChanged(BluetoothDevice pluggedDevice,
131 BluetoothHidDeviceAppConfiguration config, boolean registered) {
132 mCallback.onAppStatusChanged(pluggedDevice, config, registered);
133 }
134
135 @Override
136 public void onConnectionStateChanged(BluetoothDevice device, int state) {
137 mCallback.onConnectionStateChanged(device, state);
138 }
139
140 @Override
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000141 public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
142 mCallback.onGetReport(device, type, id, bufferSize);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530143 }
144
145 @Override
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000146 public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
147 mCallback.onSetReport(device, type, id, data);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530148 }
149
150 @Override
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000151 public void onSetProtocol(BluetoothDevice device, byte protocol) {
152 mCallback.onSetProtocol(device, protocol);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530153 }
154
155 @Override
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000156 public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
157 mCallback.onIntrData(device, reportId, data);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530158 }
159
160 @Override
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000161 public void onVirtualCableUnplug(BluetoothDevice device) {
162 mCallback.onVirtualCableUnplug(device);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530163 }
164 }
165
166 final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
167 new IBluetoothStateChangeCallback.Stub() {
168
169 public void onBluetoothStateChange(boolean up) {
170 Log.d(TAG, "onBluetoothStateChange: up=" + up);
171 synchronized (mConnection) {
172 if (!up) {
173 Log.d(TAG,"Unbinding service...");
174 if (mService != null) {
175 mService = null;
176 try {
177 mContext.unbindService(mConnection);
178 } catch (IllegalArgumentException e) {
179 Log.e(TAG,"onBluetoothStateChange: could not unbind service:", e);
180 }
181 }
182 } else {
183 try {
184 if (mService == null) {
185 Log.d(TAG,"Binding HID Device service...");
186 doBind();
187 }
188 } catch (IllegalStateException e) {
189 Log.e(TAG,"onBluetoothStateChange: could not bind to HID Dev service: ", e);
190 } catch (SecurityException e) {
191 Log.e(TAG,"onBluetoothStateChange: could not bind to HID Dev service: ", e);
192 }
193 }
194 }
195 }
196 };
197
198 private ServiceConnection mConnection = new ServiceConnection() {
199
200 public void onServiceConnected(ComponentName className, IBinder service) {
201 Log.d(TAG, "onServiceConnected()");
202
Ivan Podogov0afe1902016-12-23 11:52:21 +0000203 mService = IBluetoothInputHost.Stub.asInterface(service);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530204
205 if (mServiceListener != null) {
Ivan Podogov0afe1902016-12-23 11:52:21 +0000206 mServiceListener.onServiceConnected(BluetoothProfile.INPUT_HOST,
207 BluetoothInputHost.this);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530208 }
209 }
210
211 public void onServiceDisconnected(ComponentName className) {
212 Log.d(TAG, "onServiceDisconnected()");
213
214 mService = null;
215
216 if (mServiceListener != null) {
Ivan Podogov0afe1902016-12-23 11:52:21 +0000217 mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_HOST);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530218 }
219 }
220 };
221
Ivan Podogov0afe1902016-12-23 11:52:21 +0000222 BluetoothInputHost(Context context, ServiceListener listener) {
223 Log.v(TAG, "BluetoothInputHost");
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530224
225 mContext = context;
226 mServiceListener = listener;
227 mAdapter = BluetoothAdapter.getDefaultAdapter();
228
229 IBluetoothManager mgr = mAdapter.getBluetoothManager();
230 if (mgr != null) {
231 try {
232 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
233 } catch (RemoteException e) {
234 e.printStackTrace();
235 }
236 }
237
238 doBind();
239 }
240
241 boolean doBind() {
Ivan Podogov0afe1902016-12-23 11:52:21 +0000242 Intent intent = new Intent(IBluetoothInputHost.class.getName());
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530243 ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
244 intent.setComponent(comp);
245 if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
246 android.os.Process.myUserHandle())) {
247 Log.e(TAG, "Could not bind to Bluetooth HID Device Service with " + intent);
248 return false;
249 }
250 Log.d(TAG, "Bound to HID Device Service");
251 return true;
252 }
253
254 void close() {
255 Log.v(TAG, "close()");
256
257 IBluetoothManager mgr = mAdapter.getBluetoothManager();
258 if (mgr != null) {
259 try {
260 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
261 } catch (RemoteException e) {
262 e.printStackTrace();
263 }
264 }
265
266 synchronized (mConnection) {
267 if (mService != null) {
268 mService = null;
269 try {
270 mContext.unbindService(mConnection);
271 } catch (IllegalArgumentException e) {
272 Log.e(TAG,"close: could not unbind HID Dev service: ", e);
273 }
274 }
275 }
276
277 mServiceListener = null;
278 }
279
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000280 /**
281 * {@inheritDoc}
282 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530283 public List<BluetoothDevice> getConnectedDevices() {
284 Log.v(TAG, "getConnectedDevices()");
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000285
286 if (mService != null) {
287 try {
288 return mService.getConnectedDevices();
289 } catch (RemoteException e) {
290 Log.e(TAG, e.toString());
291 }
292 } else {
293 Log.w(TAG, "Proxy not attached to service");
294 }
295
296 return new ArrayList<BluetoothDevice>();
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530297 }
298
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000299 /**
300 * {@inheritDoc}
301 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530302 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
303 Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000304
305 if (mService != null) {
306 try {
307 return mService.getDevicesMatchingConnectionStates(states);
308 } catch (RemoteException e) {
309 Log.e(TAG, e.toString());
310 }
311 } else {
312 Log.w(TAG, "Proxy not attached to service");
313 }
314
315 return new ArrayList<BluetoothDevice>();
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530316 }
317
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000318 /**
319 * {@inheritDoc}
320 */
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530321 public int getConnectionState(BluetoothDevice device) {
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000322 Log.v(TAG, "getConnectionState(): device=" + device);
323
324 if (mService != null) {
325 try {
326 return mService.getConnectionState(device);
327 } catch (RemoteException e) {
328 Log.e(TAG, e.toString());
329 }
330 } else {
331 Log.w(TAG, "Proxy not attached to service");
332 }
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530333
334 return STATE_DISCONNECTED;
335 }
336
337 /**
338 * Registers application to be used for HID device. Connections to HID
339 * Device are only possible when application is registered. Only one
340 * application can be registered at time. When no longer used, application
341 * should be unregistered using
342 * {@link #unregisterApp(BluetoothHidDeviceAppConfiguration)}.
343 *
344 * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of
345 * HID Device SDP record.
346 * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of
347 * Incoming QoS Settings.
348 * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of
349 * Outgoing QoS Settings.
350 * @param callback {@link BluetoothHidDeviceCallback} object to which
351 * callback messages will be sent.
352 * @return
353 */
354 public boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
355 BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
356 BluetoothHidDeviceCallback callback) {
357 Log.v(TAG, "registerApp(): sdp=" + sdp + " inQos=" + inQos + " outQos=" + outQos
358 + " callback=" + callback);
359
360 boolean result = false;
361
362 if (sdp == null || callback == null) {
363 return false;
364 }
365
366 if (mService != null) {
367 try {
368 BluetoothHidDeviceAppConfiguration config =
369 new BluetoothHidDeviceAppConfiguration();
370 BluetoothHidDeviceCallbackWrapper cbw =
371 new BluetoothHidDeviceCallbackWrapper(callback);
372 result = mService.registerApp(config, sdp, inQos, outQos, cbw);
373 } catch (RemoteException e) {
374 Log.e(TAG, e.toString());
375 }
376 } else {
377 Log.w(TAG, "Proxy not attached to service");
378 }
379
380 return result;
381 }
382
383 /**
384 * Unregisters application. Active connection will be disconnected and no
385 * new connections will be allowed until registered again using
386 * {@link #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
387 *
388 * @param config {@link BluetoothHidDeviceAppConfiguration} object as
389 * obtained from
390 * {@link BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice,
391 * BluetoothHidDeviceAppConfiguration, boolean)}
392 *
393 * @return
394 */
395 public boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) {
396 Log.v(TAG, "unregisterApp()");
397
398 boolean result = false;
399
400 if (mService != null) {
401 try {
402 result = mService.unregisterApp(config);
403 } catch (RemoteException e) {
404 Log.e(TAG, e.toString());
405 }
406 } else {
407 Log.w(TAG, "Proxy not attached to service");
408 }
409
410 return result;
411 }
412
413 /**
414 * Sends report to remote host using interrupt channel.
415 *
416 * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id
417 * are not defined in descriptor.
418 * @param data Report data, not including Report Id.
419 * @return
420 */
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000421 public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530422 boolean result = false;
423
424 if (mService != null) {
425 try {
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000426 result = mService.sendReport(device, id, data);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530427 } catch (RemoteException e) {
428 Log.e(TAG, e.toString());
429 }
430 } else {
431 Log.w(TAG, "Proxy not attached to service");
432 }
433
434 return result;
435 }
436
437 /**
438 * Sends report to remote host as reply for GET_REPORT request from
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000439 * {@link BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)}.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530440 *
441 * @param type Report Type, as in request.
442 * @param id Report Id, as in request.
443 * @param data Report data, not including Report Id.
444 * @return
445 */
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000446 public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
447 Log.v(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530448
449 boolean result = false;
450
451 if (mService != null) {
452 try {
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000453 result = mService.replyReport(device, type, id, data);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530454 } catch (RemoteException e) {
455 Log.e(TAG, e.toString());
456 }
457 } else {
458 Log.w(TAG, "Proxy not attached to service");
459 }
460
461 return result;
462 }
463
464 /**
465 * Sends error handshake message as reply for invalid SET_REPORT request
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000466 * from {@link BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])}.
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530467 *
468 * @param error Error to be sent for SET_REPORT via HANDSHAKE.
469 * @return
470 */
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000471 public boolean reportError(BluetoothDevice device, byte error) {
472 Log.v(TAG, "reportError(): device=" + device + " error=" + error);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530473
474 boolean result = false;
475
476 if (mService != null) {
477 try {
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000478 result = mService.reportError(device, error);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530479 } catch (RemoteException e) {
480 Log.e(TAG, e.toString());
481 }
482 } else {
483 Log.w(TAG, "Proxy not attached to service");
484 }
485
486 return result;
487 }
488
489 /**
490 * Sends Virtual Cable Unplug to currently connected host.
491 *
492 * @return
493 */
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000494 public boolean unplug(BluetoothDevice device) {
495 Log.v(TAG, "unplug(): device=" + device);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530496
497 boolean result = false;
498
499 if (mService != null) {
500 try {
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000501 result = mService.unplug(device);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530502 } catch (RemoteException e) {
503 Log.e(TAG, e.toString());
504 }
505 } else {
506 Log.w(TAG, "Proxy not attached to service");
507 }
508
509 return result;
510 }
511
512 /**
513 * Initiates connection to host which currently has Virtual Cable
514 * established with device.
515 *
516 * @return
517 */
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000518 public boolean connect(BluetoothDevice device) {
519 Log.v(TAG, "connect(): device=" + device);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530520
521 boolean result = false;
522
523 if (mService != null) {
524 try {
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000525 result = mService.connect(device);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530526 } catch (RemoteException e) {
527 Log.e(TAG, e.toString());
528 }
529 } else {
530 Log.w(TAG, "Proxy not attached to service");
531 }
532
533 return result;
534 }
535
536 /**
537 * Disconnects from currently connected host.
538 *
539 * @return
540 */
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000541 public boolean disconnect(BluetoothDevice device) {
542 Log.v(TAG, "disconnect(): device=" + device);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530543
544 boolean result = false;
545
546 if (mService != null) {
547 try {
Ivan Podogovdd87cd32016-12-30 14:43:29 +0000548 result = mService.disconnect(device);
Hemant Guptae88fd4b2014-04-18 11:22:45 +0530549 } catch (RemoteException e) {
550 Log.e(TAG, e.toString());
551 }
552 } else {
553 Log.w(TAG, "Proxy not attached to service");
554 }
555
556 return result;
557 }
558}