1 /* 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 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.IBinder; 26 import android.os.RemoteException; 27 import android.util.Log; 28 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.List; 32 33 /** 34 * @hide 35 */ 36 public final class BluetoothInputHost implements BluetoothProfile { 37 38 private static final String TAG = BluetoothInputHost.class.getSimpleName(); 39 40 /** 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) 59 public static final String ACTION_CONNECTION_STATE_CHANGED = 60 "android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED"; 61 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 117 private volatile IBluetoothInputHost mService; 118 119 private BluetoothAdapter mAdapter; 120 121 private static class BluetoothHidDeviceCallbackWrapper extends IBluetoothHidDeviceCallback.Stub { 122 123 private BluetoothHidDeviceCallback mCallback; 124 BluetoothHidDeviceCallbackWrapper(BluetoothHidDeviceCallback callback)125 public BluetoothHidDeviceCallbackWrapper(BluetoothHidDeviceCallback callback) { 126 mCallback = callback; 127 } 128 129 @Override onAppStatusChanged(BluetoothDevice pluggedDevice, BluetoothHidDeviceAppConfiguration config, boolean registered)130 public void onAppStatusChanged(BluetoothDevice pluggedDevice, 131 BluetoothHidDeviceAppConfiguration config, boolean registered) { 132 mCallback.onAppStatusChanged(pluggedDevice, config, registered); 133 } 134 135 @Override onConnectionStateChanged(BluetoothDevice device, int state)136 public void onConnectionStateChanged(BluetoothDevice device, int state) { 137 mCallback.onConnectionStateChanged(device, state); 138 } 139 140 @Override onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize)141 public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) { 142 mCallback.onGetReport(device, type, id, bufferSize); 143 } 144 145 @Override onSetReport(BluetoothDevice device, byte type, byte id, byte[] data)146 public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) { 147 mCallback.onSetReport(device, type, id, data); 148 } 149 150 @Override onSetProtocol(BluetoothDevice device, byte protocol)151 public void onSetProtocol(BluetoothDevice device, byte protocol) { 152 mCallback.onSetProtocol(device, protocol); 153 } 154 155 @Override onIntrData(BluetoothDevice device, byte reportId, byte[] data)156 public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) { 157 mCallback.onIntrData(device, reportId, data); 158 } 159 160 @Override onVirtualCableUnplug(BluetoothDevice device)161 public void onVirtualCableUnplug(BluetoothDevice device) { 162 mCallback.onVirtualCableUnplug(device); 163 } 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 final ServiceConnection mConnection = new ServiceConnection() { 199 public void onServiceConnected(ComponentName className, IBinder service) { 200 Log.d(TAG, "onServiceConnected()"); 201 mService = IBluetoothInputHost.Stub.asInterface(service); 202 if (mServiceListener != null) { 203 mServiceListener.onServiceConnected(BluetoothProfile.INPUT_HOST, 204 BluetoothInputHost.this); 205 } 206 } 207 public void onServiceDisconnected(ComponentName className) { 208 Log.d(TAG, "onServiceDisconnected()"); 209 mService = null; 210 if (mServiceListener != null) { 211 mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_HOST); 212 } 213 } 214 }; 215 BluetoothInputHost(Context context, ServiceListener listener)216 BluetoothInputHost(Context context, ServiceListener listener) { 217 Log.v(TAG, "BluetoothInputHost"); 218 219 mContext = context; 220 mServiceListener = listener; 221 mAdapter = BluetoothAdapter.getDefaultAdapter(); 222 223 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 224 if (mgr != null) { 225 try { 226 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); 227 } catch (RemoteException e) { 228 e.printStackTrace(); 229 } 230 } 231 232 doBind(); 233 } 234 doBind()235 boolean doBind() { 236 Intent intent = new Intent(IBluetoothInputHost.class.getName()); 237 ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); 238 intent.setComponent(comp); 239 if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, 240 android.os.Process.myUserHandle())) { 241 Log.e(TAG, "Could not bind to Bluetooth HID Device Service with " + intent); 242 return false; 243 } 244 Log.d(TAG, "Bound to HID Device Service"); 245 return true; 246 } 247 close()248 void close() { 249 Log.v(TAG, "close()"); 250 251 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 252 if (mgr != null) { 253 try { 254 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); 255 } catch (RemoteException e) { 256 e.printStackTrace(); 257 } 258 } 259 260 synchronized (mConnection) { 261 if (mService != null) { 262 mService = null; 263 try { 264 mContext.unbindService(mConnection); 265 } catch (IllegalArgumentException e) { 266 Log.e(TAG,"close: could not unbind HID Dev service: ", e); 267 } 268 } 269 } 270 271 mServiceListener = null; 272 } 273 274 /** 275 * {@inheritDoc} 276 */ getConnectedDevices()277 public List<BluetoothDevice> getConnectedDevices() { 278 Log.v(TAG, "getConnectedDevices()"); 279 280 final IBluetoothInputHost service = mService; 281 if (service != null) { 282 try { 283 return service.getConnectedDevices(); 284 } catch (RemoteException e) { 285 Log.e(TAG, e.toString()); 286 } 287 } else { 288 Log.w(TAG, "Proxy not attached to service"); 289 } 290 291 return new ArrayList<BluetoothDevice>(); 292 } 293 294 /** 295 * {@inheritDoc} 296 */ getDevicesMatchingConnectionStates(int[] states)297 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 298 Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states)); 299 300 final IBluetoothInputHost service = mService; 301 if (service != null) { 302 try { 303 return service.getDevicesMatchingConnectionStates(states); 304 } catch (RemoteException e) { 305 Log.e(TAG, e.toString()); 306 } 307 } else { 308 Log.w(TAG, "Proxy not attached to service"); 309 } 310 311 return new ArrayList<BluetoothDevice>(); 312 } 313 314 /** 315 * {@inheritDoc} 316 */ getConnectionState(BluetoothDevice device)317 public int getConnectionState(BluetoothDevice device) { 318 Log.v(TAG, "getConnectionState(): device=" + device); 319 320 final IBluetoothInputHost service = mService; 321 if (service != null) { 322 try { 323 return service.getConnectionState(device); 324 } catch (RemoteException e) { 325 Log.e(TAG, e.toString()); 326 } 327 } else { 328 Log.w(TAG, "Proxy not attached to service"); 329 } 330 331 return STATE_DISCONNECTED; 332 } 333 334 /** 335 * Registers application to be used for HID device. Connections to HID 336 * Device are only possible when application is registered. Only one 337 * application can be registered at time. When no longer used, application 338 * should be unregistered using 339 * {@link #unregisterApp(BluetoothHidDeviceAppConfiguration)}. 340 * 341 * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of 342 * HID Device SDP record. 343 * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of 344 * Incoming QoS Settings. 345 * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of 346 * Outgoing QoS Settings. 347 * @param callback {@link BluetoothHidDeviceCallback} object to which 348 * callback messages will be sent. 349 * @return 350 */ registerApp(BluetoothHidDeviceAppSdpSettings sdp, BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos, BluetoothHidDeviceCallback callback)351 public boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp, 352 BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos, 353 BluetoothHidDeviceCallback callback) { 354 Log.v(TAG, "registerApp(): sdp=" + sdp + " inQos=" + inQos + " outQos=" + outQos 355 + " callback=" + callback); 356 357 boolean result = false; 358 359 if (sdp == null || callback == null) { 360 return false; 361 } 362 363 final IBluetoothInputHost service = mService; 364 if (service != null) { 365 try { 366 BluetoothHidDeviceAppConfiguration config = 367 new BluetoothHidDeviceAppConfiguration(); 368 BluetoothHidDeviceCallbackWrapper cbw = 369 new BluetoothHidDeviceCallbackWrapper(callback); 370 result = service.registerApp(config, sdp, inQos, outQos, cbw); 371 } catch (RemoteException e) { 372 Log.e(TAG, e.toString()); 373 } 374 } else { 375 Log.w(TAG, "Proxy not attached to service"); 376 } 377 378 return result; 379 } 380 381 /** 382 * Unregisters application. Active connection will be disconnected and no 383 * new connections will be allowed until registered again using 384 * {@link #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)} 385 * 386 * @param config {@link BluetoothHidDeviceAppConfiguration} object as 387 * obtained from 388 * {@link BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice, 389 * BluetoothHidDeviceAppConfiguration, boolean)} 390 * 391 * @return 392 */ unregisterApp(BluetoothHidDeviceAppConfiguration config)393 public boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) { 394 Log.v(TAG, "unregisterApp()"); 395 396 boolean result = false; 397 398 final IBluetoothInputHost service = mService; 399 if (service != null) { 400 try { 401 result = service.unregisterApp(config); 402 } catch (RemoteException e) { 403 Log.e(TAG, e.toString()); 404 } 405 } else { 406 Log.w(TAG, "Proxy not attached to service"); 407 } 408 409 return result; 410 } 411 412 /** 413 * Sends report to remote host using interrupt channel. 414 * 415 * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id 416 * are not defined in descriptor. 417 * @param data Report data, not including Report Id. 418 * @return 419 */ sendReport(BluetoothDevice device, int id, byte[] data)420 public boolean sendReport(BluetoothDevice device, int id, byte[] data) { 421 boolean result = false; 422 423 final IBluetoothInputHost service = mService; 424 if (service != null) { 425 try { 426 result = service.sendReport(device, id, data); 427 } 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 439 * {@link BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)}. 440 * 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 */ replyReport(BluetoothDevice device, byte type, byte id, byte[] data)446 public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) { 447 Log.v(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id); 448 449 boolean result = false; 450 451 final IBluetoothInputHost service = mService; 452 if (service != null) { 453 try { 454 result = service.replyReport(device, type, id, data); 455 } catch (RemoteException e) { 456 Log.e(TAG, e.toString()); 457 } 458 } else { 459 Log.w(TAG, "Proxy not attached to service"); 460 } 461 462 return result; 463 } 464 465 /** 466 * Sends error handshake message as reply for invalid SET_REPORT request 467 * from {@link BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])}. 468 * 469 * @param error Error to be sent for SET_REPORT via HANDSHAKE. 470 * @return 471 */ reportError(BluetoothDevice device, byte error)472 public boolean reportError(BluetoothDevice device, byte error) { 473 Log.v(TAG, "reportError(): device=" + device + " error=" + error); 474 475 boolean result = false; 476 477 final IBluetoothInputHost service = mService; 478 if (service != null) { 479 try { 480 result = service.reportError(device, error); 481 } catch (RemoteException e) { 482 Log.e(TAG, e.toString()); 483 } 484 } else { 485 Log.w(TAG, "Proxy not attached to service"); 486 } 487 488 return result; 489 } 490 491 /** 492 * Sends Virtual Cable Unplug to currently connected host. 493 * 494 * @return 495 */ unplug(BluetoothDevice device)496 public boolean unplug(BluetoothDevice device) { 497 Log.v(TAG, "unplug(): device=" + device); 498 499 boolean result = false; 500 501 final IBluetoothInputHost service = mService; 502 if (service != null) { 503 try { 504 result = service.unplug(device); 505 } catch (RemoteException e) { 506 Log.e(TAG, e.toString()); 507 } 508 } else { 509 Log.w(TAG, "Proxy not attached to service"); 510 } 511 512 return result; 513 } 514 515 /** 516 * Initiates connection to host which currently has Virtual Cable 517 * established with device. 518 * 519 * @return 520 */ connect(BluetoothDevice device)521 public boolean connect(BluetoothDevice device) { 522 Log.v(TAG, "connect(): device=" + device); 523 524 boolean result = false; 525 526 final IBluetoothInputHost service = mService; 527 if (service != null) { 528 try { 529 result = service.connect(device); 530 } catch (RemoteException e) { 531 Log.e(TAG, e.toString()); 532 } 533 } else { 534 Log.w(TAG, "Proxy not attached to service"); 535 } 536 537 return result; 538 } 539 540 /** 541 * Disconnects from currently connected host. 542 * 543 * @return 544 */ disconnect(BluetoothDevice device)545 public boolean disconnect(BluetoothDevice device) { 546 Log.v(TAG, "disconnect(): device=" + device); 547 548 boolean result = false; 549 550 final IBluetoothInputHost service = mService; 551 if (service != null) { 552 try { 553 result = service.disconnect(device); 554 } catch (RemoteException e) { 555 Log.e(TAG, e.toString()); 556 } 557 } else { 558 Log.w(TAG, "Proxy not attached to service"); 559 } 560 561 return result; 562 } 563 } 564