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.Arrays; 30 import java.util.ArrayList; 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 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 ServiceConnection mConnection = new ServiceConnection() { 199 200 public void onServiceConnected(ComponentName className, IBinder service) { 201 Log.d(TAG, "onServiceConnected()"); 202 203 mService = IBluetoothInputHost.Stub.asInterface(service); 204 205 if (mServiceListener != null) { 206 mServiceListener.onServiceConnected(BluetoothProfile.INPUT_HOST, 207 BluetoothInputHost.this); 208 } 209 } 210 211 public void onServiceDisconnected(ComponentName className) { 212 Log.d(TAG, "onServiceDisconnected()"); 213 214 mService = null; 215 216 if (mServiceListener != null) { 217 mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_HOST); 218 } 219 } 220 }; 221 BluetoothInputHost(Context context, ServiceListener listener)222 BluetoothInputHost(Context context, ServiceListener listener) { 223 Log.v(TAG, "BluetoothInputHost"); 224 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 doBind()241 boolean doBind() { 242 Intent intent = new Intent(IBluetoothInputHost.class.getName()); 243 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 close()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 280 /** 281 * {@inheritDoc} 282 */ getConnectedDevices()283 public List<BluetoothDevice> getConnectedDevices() { 284 Log.v(TAG, "getConnectedDevices()"); 285 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>(); 297 } 298 299 /** 300 * {@inheritDoc} 301 */ getDevicesMatchingConnectionStates(int[] states)302 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 303 Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states)); 304 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>(); 316 } 317 318 /** 319 * {@inheritDoc} 320 */ getConnectionState(BluetoothDevice device)321 public int getConnectionState(BluetoothDevice device) { 322 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 } 333 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 */ registerApp(BluetoothHidDeviceAppSdpSettings sdp, BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos, BluetoothHidDeviceCallback callback)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 */ unregisterApp(BluetoothHidDeviceAppConfiguration config)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 */ sendReport(BluetoothDevice device, int id, byte[] data)421 public boolean sendReport(BluetoothDevice device, int id, byte[] data) { 422 boolean result = false; 423 424 if (mService != null) { 425 try { 426 result = mService.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 if (mService != null) { 452 try { 453 result = mService.replyReport(device, type, id, data); 454 } 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 466 * from {@link BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])}. 467 * 468 * @param error Error to be sent for SET_REPORT via HANDSHAKE. 469 * @return 470 */ reportError(BluetoothDevice device, byte error)471 public boolean reportError(BluetoothDevice device, byte error) { 472 Log.v(TAG, "reportError(): device=" + device + " error=" + error); 473 474 boolean result = false; 475 476 if (mService != null) { 477 try { 478 result = mService.reportError(device, error); 479 } 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 */ unplug(BluetoothDevice device)494 public boolean unplug(BluetoothDevice device) { 495 Log.v(TAG, "unplug(): device=" + device); 496 497 boolean result = false; 498 499 if (mService != null) { 500 try { 501 result = mService.unplug(device); 502 } 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 */ connect(BluetoothDevice device)518 public boolean connect(BluetoothDevice device) { 519 Log.v(TAG, "connect(): device=" + device); 520 521 boolean result = false; 522 523 if (mService != null) { 524 try { 525 result = mService.connect(device); 526 } 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 */ disconnect(BluetoothDevice device)541 public boolean disconnect(BluetoothDevice device) { 542 Log.v(TAG, "disconnect(): device=" + device); 543 544 boolean result = false; 545 546 if (mService != null) { 547 try { 548 result = mService.disconnect(device); 549 } 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 } 559