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.Manifest; 20 import android.annotation.NonNull; 21 import android.annotation.RequiresPermission; 22 import android.annotation.SdkConstant; 23 import android.annotation.SdkConstant.SdkConstantType; 24 import android.annotation.SystemApi; 25 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 26 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; 27 import android.content.Attributable; 28 import android.content.AttributionSource; 29 import android.content.Context; 30 import android.os.Binder; 31 import android.os.IBinder; 32 import android.os.RemoteException; 33 import android.util.Log; 34 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.concurrent.Executor; 38 39 /** 40 * Provides the public APIs to control the Bluetooth HID Device profile. 41 * 42 * <p>BluetoothHidDevice is a proxy object for controlling the Bluetooth HID Device Service via IPC. 43 * Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothHidDevice proxy object. 44 */ 45 public final class BluetoothHidDevice implements BluetoothProfile { 46 private static final String TAG = BluetoothHidDevice.class.getSimpleName(); 47 private static final boolean DBG = false; 48 49 /** 50 * Intent used to broadcast the change in connection state of the Input Host profile. 51 * 52 * <p>This intent will have 3 extras: 53 * 54 * <ul> 55 * <li>{@link #EXTRA_STATE} - The current state of the profile. 56 * <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. 57 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. 58 * </ul> 59 * 60 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link 61 * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link 62 * #STATE_DISCONNECTING}. 63 */ 64 @RequiresLegacyBluetoothPermission 65 @RequiresBluetoothConnectPermission 66 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 67 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 68 public static final String ACTION_CONNECTION_STATE_CHANGED = 69 "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED"; 70 71 /** 72 * Constant representing unspecified HID device subclass. 73 * 74 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, 75 * BluetoothHidDeviceAppQosSettings, Executor, Callback) 76 */ 77 public static final byte SUBCLASS1_NONE = (byte) 0x00; 78 /** 79 * Constant representing keyboard subclass. 80 * 81 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, 82 * BluetoothHidDeviceAppQosSettings, Executor, Callback) 83 */ 84 public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40; 85 /** 86 * Constant representing mouse subclass. 87 * 88 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, 89 * BluetoothHidDeviceAppQosSettings, Executor, Callback) 90 */ 91 public static final byte SUBCLASS1_MOUSE = (byte) 0x80; 92 /** 93 * Constant representing combo keyboard and mouse subclass. 94 * 95 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, 96 * BluetoothHidDeviceAppQosSettings, Executor, Callback) 97 */ 98 public static final byte SUBCLASS1_COMBO = (byte) 0xC0; 99 100 /** 101 * Constant representing uncategorized HID device subclass. 102 * 103 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, 104 * BluetoothHidDeviceAppQosSettings, Executor, Callback) 105 */ 106 public static final byte SUBCLASS2_UNCATEGORIZED = (byte) 0x00; 107 /** 108 * Constant representing joystick subclass. 109 * 110 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, 111 * BluetoothHidDeviceAppQosSettings, Executor, Callback) 112 */ 113 public static final byte SUBCLASS2_JOYSTICK = (byte) 0x01; 114 /** 115 * Constant representing gamepad subclass. 116 * 117 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, 118 * BluetoothHidDeviceAppQosSettings, Executor, Callback) 119 */ 120 public static final byte SUBCLASS2_GAMEPAD = (byte) 0x02; 121 /** 122 * Constant representing remote control subclass. 123 * 124 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, 125 * BluetoothHidDeviceAppQosSettings, Executor, Callback) 126 */ 127 public static final byte SUBCLASS2_REMOTE_CONTROL = (byte) 0x03; 128 /** 129 * Constant representing sensing device subclass. 130 * 131 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, 132 * BluetoothHidDeviceAppQosSettings, Executor, Callback) 133 */ 134 public static final byte SUBCLASS2_SENSING_DEVICE = (byte) 0x04; 135 /** 136 * Constant representing digitizer tablet subclass. 137 * 138 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, 139 * BluetoothHidDeviceAppQosSettings, Executor, Callback) 140 */ 141 public static final byte SUBCLASS2_DIGITIZER_TABLET = (byte) 0x05; 142 /** 143 * Constant representing card reader subclass. 144 * 145 * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, 146 * BluetoothHidDeviceAppQosSettings, Executor, Callback) 147 */ 148 public static final byte SUBCLASS2_CARD_READER = (byte) 0x06; 149 150 /** 151 * Constant representing HID Input 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 */ 157 public static final byte REPORT_TYPE_INPUT = (byte) 1; 158 /** 159 * Constant representing HID Output 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 */ 165 public static final byte REPORT_TYPE_OUTPUT = (byte) 2; 166 /** 167 * Constant representing HID Feature Report type. 168 * 169 * @see Callback#onGetReport(BluetoothDevice, byte, byte, int) 170 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) 171 * @see Callback#onInterruptData(BluetoothDevice, byte, byte[]) 172 */ 173 public static final byte REPORT_TYPE_FEATURE = (byte) 3; 174 175 /** 176 * Constant representing success response for Set Report. 177 * 178 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) 179 */ 180 public static final byte ERROR_RSP_SUCCESS = (byte) 0; 181 /** 182 * Constant representing error response for Set Report due to "not ready". 183 * 184 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) 185 */ 186 public static final byte ERROR_RSP_NOT_READY = (byte) 1; 187 /** 188 * Constant representing error response for Set Report due to "invalid report ID". 189 * 190 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) 191 */ 192 public static final byte ERROR_RSP_INVALID_RPT_ID = (byte) 2; 193 /** 194 * Constant representing error response for Set Report due to "unsupported request". 195 * 196 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) 197 */ 198 public static final byte ERROR_RSP_UNSUPPORTED_REQ = (byte) 3; 199 /** 200 * Constant representing error response for Set Report due to "invalid parameter". 201 * 202 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) 203 */ 204 public static final byte ERROR_RSP_INVALID_PARAM = (byte) 4; 205 /** 206 * Constant representing error response for Set Report with unknown reason. 207 * 208 * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) 209 */ 210 public static final byte ERROR_RSP_UNKNOWN = (byte) 14; 211 212 /** 213 * Constant representing boot protocol mode used set by host. Default is always {@link 214 * #PROTOCOL_REPORT_MODE} unless notified otherwise. 215 * 216 * @see Callback#onSetProtocol(BluetoothDevice, byte) 217 */ 218 public static final byte PROTOCOL_BOOT_MODE = (byte) 0; 219 /** 220 * Constant representing report protocol mode used set by host. Default is always {@link 221 * #PROTOCOL_REPORT_MODE} unless notified otherwise. 222 * 223 * @see Callback#onSetProtocol(BluetoothDevice, byte) 224 */ 225 public static final byte PROTOCOL_REPORT_MODE = (byte) 1; 226 227 /** 228 * The template class that applications use to call callback functions on events from the HID 229 * host. Callback functions are wrapped in this class and registered to the Android system 230 * during app registration. 231 */ 232 public abstract static class Callback { 233 234 private static final String TAG = "BluetoothHidDevCallback"; 235 236 /** 237 * Callback called when application registration state changes. Usually it's called due to 238 * either {@link BluetoothHidDevice#registerApp (String, String, String, byte, byte[], 239 * Executor, Callback)} or {@link BluetoothHidDevice#unregisterApp()} , but can be also 240 * unsolicited in case e.g. Bluetooth was turned off in which case application is 241 * unregistered automatically. 242 * 243 * @param pluggedDevice {@link BluetoothDevice} object which represents host that currently 244 * has Virtual Cable established with device. Only valid when application is registered, 245 * can be <code>null</code>. 246 * @param registered <code>true</code> if application is registered, <code>false</code> 247 * otherwise. 248 */ onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered)249 public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) { 250 Log.d( 251 TAG, 252 "onAppStatusChanged: pluggedDevice=" 253 + pluggedDevice 254 + " registered=" 255 + registered); 256 } 257 258 /** 259 * Callback called when connection state with remote host was changed. Application can 260 * assume than Virtual Cable is established when called with {@link 261 * BluetoothProfile#STATE_CONNECTED} <code>state</code>. 262 * 263 * @param device {@link BluetoothDevice} object representing host device which connection 264 * state was changed. 265 * @param state Connection state as defined in {@link BluetoothProfile}. 266 */ onConnectionStateChanged(BluetoothDevice device, int state)267 public void onConnectionStateChanged(BluetoothDevice device, int state) { 268 Log.d(TAG, "onConnectionStateChanged: device=" + device + " state=" + state); 269 } 270 271 /** 272 * Callback called when GET_REPORT is received from remote host. Should be replied by 273 * application using {@link BluetoothHidDevice#replyReport(BluetoothDevice, byte, byte, 274 * byte[])}. 275 * 276 * @param type Requested Report Type. 277 * @param id Requested Report Id, can be 0 if no Report Id are defined in descriptor. 278 * @param bufferSize Requested buffer size, application shall respond with at least given 279 * number of bytes. 280 */ onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize)281 public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) { 282 Log.d( 283 TAG, 284 "onGetReport: device=" 285 + device 286 + " type=" 287 + type 288 + " id=" 289 + id 290 + " bufferSize=" 291 + bufferSize); 292 } 293 294 /** 295 * Callback called when SET_REPORT is received from remote host. In case received data are 296 * invalid, application shall respond with {@link 297 * BluetoothHidDevice#reportError(BluetoothDevice, byte)}. 298 * 299 * @param type Report Type. 300 * @param id Report Id. 301 * @param data Report data. 302 */ onSetReport(BluetoothDevice device, byte type, byte id, byte[] data)303 public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) { 304 Log.d(TAG, "onSetReport: device=" + device + " type=" + type + " id=" + id); 305 } 306 307 /** 308 * Callback called when SET_PROTOCOL is received from remote host. Application shall use 309 * this information to send only reports valid for given protocol mode. By default, {@link 310 * BluetoothHidDevice#PROTOCOL_REPORT_MODE} shall be assumed. 311 * 312 * @param protocol Protocol Mode. 313 */ onSetProtocol(BluetoothDevice device, byte protocol)314 public void onSetProtocol(BluetoothDevice device, byte protocol) { 315 Log.d(TAG, "onSetProtocol: device=" + device + " protocol=" + protocol); 316 } 317 318 /** 319 * Callback called when report data is received over interrupt channel. Report Type is 320 * assumed to be {@link BluetoothHidDevice#REPORT_TYPE_OUTPUT}. 321 * 322 * @param reportId Report Id. 323 * @param data Report data. 324 */ onInterruptData(BluetoothDevice device, byte reportId, byte[] data)325 public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) { 326 Log.d(TAG, "onInterruptData: device=" + device + " reportId=" + reportId); 327 } 328 329 /** 330 * Callback called when Virtual Cable is removed. After this callback is received connection 331 * will be disconnected automatically. 332 */ onVirtualCableUnplug(BluetoothDevice device)333 public void onVirtualCableUnplug(BluetoothDevice device) { 334 Log.d(TAG, "onVirtualCableUnplug: device=" + device); 335 } 336 } 337 338 private static class CallbackWrapper extends IBluetoothHidDeviceCallback.Stub { 339 340 private final Executor mExecutor; 341 private final Callback mCallback; 342 private final AttributionSource mAttributionSource; 343 CallbackWrapper(Executor executor, Callback callback, AttributionSource attributionSource)344 CallbackWrapper(Executor executor, Callback callback, AttributionSource attributionSource) { 345 mExecutor = executor; 346 mCallback = callback; 347 mAttributionSource = attributionSource; 348 } 349 350 @Override onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered)351 public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) { 352 Attributable.setAttributionSource(pluggedDevice, mAttributionSource); 353 final long token = clearCallingIdentity(); 354 try { 355 mExecutor.execute(() -> mCallback.onAppStatusChanged(pluggedDevice, registered)); 356 } finally { 357 restoreCallingIdentity(token); 358 } 359 } 360 361 @Override onConnectionStateChanged(BluetoothDevice device, int state)362 public void onConnectionStateChanged(BluetoothDevice device, int state) { 363 Attributable.setAttributionSource(device, mAttributionSource); 364 final long token = clearCallingIdentity(); 365 try { 366 mExecutor.execute(() -> mCallback.onConnectionStateChanged(device, state)); 367 } finally { 368 restoreCallingIdentity(token); 369 } 370 } 371 372 @Override onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize)373 public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) { 374 Attributable.setAttributionSource(device, mAttributionSource); 375 final long token = clearCallingIdentity(); 376 try { 377 mExecutor.execute(() -> mCallback.onGetReport(device, type, id, bufferSize)); 378 } finally { 379 restoreCallingIdentity(token); 380 } 381 } 382 383 @Override onSetReport(BluetoothDevice device, byte type, byte id, byte[] data)384 public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) { 385 Attributable.setAttributionSource(device, mAttributionSource); 386 final long token = clearCallingIdentity(); 387 try { 388 mExecutor.execute(() -> mCallback.onSetReport(device, type, id, data)); 389 } finally { 390 restoreCallingIdentity(token); 391 } 392 } 393 394 @Override onSetProtocol(BluetoothDevice device, byte protocol)395 public void onSetProtocol(BluetoothDevice device, byte protocol) { 396 Attributable.setAttributionSource(device, mAttributionSource); 397 final long token = clearCallingIdentity(); 398 try { 399 mExecutor.execute(() -> mCallback.onSetProtocol(device, protocol)); 400 } finally { 401 restoreCallingIdentity(token); 402 } 403 } 404 405 @Override onInterruptData(BluetoothDevice device, byte reportId, byte[] data)406 public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) { 407 Attributable.setAttributionSource(device, mAttributionSource); 408 final long token = clearCallingIdentity(); 409 try { 410 mExecutor.execute(() -> mCallback.onInterruptData(device, reportId, data)); 411 } finally { 412 restoreCallingIdentity(token); 413 } 414 } 415 416 @Override onVirtualCableUnplug(BluetoothDevice device)417 public void onVirtualCableUnplug(BluetoothDevice device) { 418 Attributable.setAttributionSource(device, mAttributionSource); 419 final long token = clearCallingIdentity(); 420 try { 421 mExecutor.execute(() -> mCallback.onVirtualCableUnplug(device)); 422 } finally { 423 restoreCallingIdentity(token); 424 } 425 } 426 } 427 428 private final BluetoothAdapter mAdapter; 429 private final AttributionSource mAttributionSource; 430 private final BluetoothProfileConnector<IBluetoothHidDevice> mProfileConnector = 431 new BluetoothProfileConnector(this, BluetoothProfile.HID_DEVICE, 432 "BluetoothHidDevice", IBluetoothHidDevice.class.getName()) { 433 @Override 434 public IBluetoothHidDevice getServiceInterface(IBinder service) { 435 return IBluetoothHidDevice.Stub.asInterface(Binder.allowBlocking(service)); 436 } 437 }; 438 BluetoothHidDevice(Context context, ServiceListener listener, BluetoothAdapter adapter)439 BluetoothHidDevice(Context context, ServiceListener listener, BluetoothAdapter adapter) { 440 mAdapter = adapter; 441 mAttributionSource = adapter.getAttributionSource(); 442 mProfileConnector.connect(context, listener); 443 } 444 close()445 void close() { 446 mProfileConnector.disconnect(); 447 } 448 getService()449 private IBluetoothHidDevice getService() { 450 return mProfileConnector.getService(); 451 } 452 453 /** {@inheritDoc} */ 454 @Override 455 @RequiresBluetoothConnectPermission 456 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getConnectedDevices()457 public List<BluetoothDevice> getConnectedDevices() { 458 final IBluetoothHidDevice service = getService(); 459 if (service != null) { 460 try { 461 return Attributable.setAttributionSource( 462 service.getConnectedDevices(mAttributionSource), mAttributionSource); 463 } catch (RemoteException e) { 464 Log.e(TAG, e.toString()); 465 } 466 } else { 467 Log.w(TAG, "Proxy not attached to service"); 468 } 469 470 return new ArrayList<>(); 471 } 472 473 /** {@inheritDoc} */ 474 @Override 475 @RequiresBluetoothConnectPermission 476 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getDevicesMatchingConnectionStates(int[] states)477 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 478 final IBluetoothHidDevice service = getService(); 479 if (service != null) { 480 try { 481 return Attributable.setAttributionSource( 482 service.getDevicesMatchingConnectionStates(states, mAttributionSource), 483 mAttributionSource); 484 } catch (RemoteException e) { 485 Log.e(TAG, e.toString()); 486 } 487 } else { 488 Log.w(TAG, "Proxy not attached to service"); 489 } 490 491 return new ArrayList<>(); 492 } 493 494 /** {@inheritDoc} */ 495 @Override 496 @RequiresBluetoothConnectPermission 497 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getConnectionState(BluetoothDevice device)498 public int getConnectionState(BluetoothDevice device) { 499 final IBluetoothHidDevice service = getService(); 500 if (service != null) { 501 try { 502 return service.getConnectionState(device, mAttributionSource); 503 } catch (RemoteException e) { 504 Log.e(TAG, e.toString()); 505 } 506 } else { 507 Log.w(TAG, "Proxy not attached to service"); 508 } 509 510 return STATE_DISCONNECTED; 511 } 512 513 /** 514 * Registers application to be used for HID device. Connections to HID Device are only possible 515 * when application is registered. Only one application can be registered at one time. When an 516 * application is registered, the HID Host service will be disabled until it is unregistered. 517 * When no longer used, application should be unregistered using {@link #unregisterApp()}. The 518 * app will be automatically unregistered if it is not foreground. The registration status 519 * should be tracked by the application by handling callback from Callback#onAppStatusChanged. 520 * The app registration status is not related to the return value of this method. 521 * 522 * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of HID Device SDP record. The HID 523 * Device SDP record is required. 524 * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of Incoming QoS Settings. The 525 * Incoming QoS Settings is not required. Use null or default 526 * BluetoothHidDeviceAppQosSettings.Builder for default values. 527 * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of Outgoing QoS Settings. The 528 * Outgoing QoS Settings is not required. Use null or default 529 * BluetoothHidDeviceAppQosSettings.Builder for default values. 530 * @param executor {@link Executor} object on which callback will be executed. The Executor 531 * object is required. 532 * @param callback {@link Callback} object to which callback messages will be sent. The Callback 533 * object is required. 534 * @return true if the command is successfully sent; otherwise false. 535 */ 536 @RequiresBluetoothConnectPermission 537 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) registerApp( BluetoothHidDeviceAppSdpSettings sdp, BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos, Executor executor, Callback callback)538 public boolean registerApp( 539 BluetoothHidDeviceAppSdpSettings sdp, 540 BluetoothHidDeviceAppQosSettings inQos, 541 BluetoothHidDeviceAppQosSettings outQos, 542 Executor executor, 543 Callback callback) { 544 boolean result = false; 545 546 if (sdp == null) { 547 throw new IllegalArgumentException("sdp parameter cannot be null"); 548 } 549 550 if (executor == null) { 551 throw new IllegalArgumentException("executor parameter cannot be null"); 552 } 553 554 if (callback == null) { 555 throw new IllegalArgumentException("callback parameter cannot be null"); 556 } 557 558 final IBluetoothHidDevice service = getService(); 559 if (service != null) { 560 try { 561 CallbackWrapper cbw = new CallbackWrapper(executor, callback, mAttributionSource); 562 result = service.registerApp(sdp, inQos, outQos, cbw, mAttributionSource); 563 } catch (RemoteException e) { 564 Log.e(TAG, e.toString()); 565 } 566 } else { 567 Log.w(TAG, "Proxy not attached to service"); 568 } 569 570 return result; 571 } 572 573 /** 574 * Unregisters application. Active connection will be disconnected and no new connections will 575 * be allowed until registered again using {@link #registerApp 576 * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, 577 * BluetoothHidDeviceAppQosSettings, Executor, Callback)}. The registration status should be 578 * tracked by the application by handling callback from Callback#onAppStatusChanged. The app 579 * registration status is not related to the return value of this method. 580 * 581 * @return true if the command is successfully sent; otherwise false. 582 */ 583 @RequiresBluetoothConnectPermission 584 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) unregisterApp()585 public boolean unregisterApp() { 586 boolean result = false; 587 588 final IBluetoothHidDevice service = getService(); 589 if (service != null) { 590 try { 591 result = service.unregisterApp(mAttributionSource); 592 } catch (RemoteException e) { 593 Log.e(TAG, e.toString()); 594 } 595 } else { 596 Log.w(TAG, "Proxy not attached to service"); 597 } 598 599 return result; 600 } 601 602 /** 603 * Sends report to remote host using interrupt channel. 604 * 605 * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id are not defined in 606 * descriptor. 607 * @param data Report data, not including Report Id. 608 * @return true if the command is successfully sent; otherwise false. 609 */ 610 @RequiresBluetoothConnectPermission 611 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) sendReport(BluetoothDevice device, int id, byte[] data)612 public boolean sendReport(BluetoothDevice device, int id, byte[] data) { 613 boolean result = false; 614 615 final IBluetoothHidDevice service = getService(); 616 if (service != null) { 617 try { 618 result = service.sendReport(device, id, data, mAttributionSource); 619 } catch (RemoteException e) { 620 Log.e(TAG, e.toString()); 621 } 622 } else { 623 Log.w(TAG, "Proxy not attached to service"); 624 } 625 626 return result; 627 } 628 629 /** 630 * Sends report to remote host as reply for GET_REPORT request from {@link 631 * Callback#onGetReport(BluetoothDevice, byte, byte, int)}. 632 * 633 * @param type Report Type, as in request. 634 * @param id Report Id, as in request. 635 * @param data Report data, not including Report Id. 636 * @return true if the command is successfully sent; otherwise false. 637 */ 638 @RequiresBluetoothConnectPermission 639 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) replyReport(BluetoothDevice device, byte type, byte id, byte[] data)640 public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) { 641 boolean result = false; 642 643 final IBluetoothHidDevice service = getService(); 644 if (service != null) { 645 try { 646 result = service.replyReport(device, type, id, data, mAttributionSource); 647 } catch (RemoteException e) { 648 Log.e(TAG, e.toString()); 649 } 650 } else { 651 Log.w(TAG, "Proxy not attached to service"); 652 } 653 654 return result; 655 } 656 657 /** 658 * Sends error handshake message as reply for invalid SET_REPORT request from {@link 659 * Callback#onSetReport(BluetoothDevice, byte, byte, byte[])}. 660 * 661 * @param error Error to be sent for SET_REPORT via HANDSHAKE. 662 * @return true if the command is successfully sent; otherwise false. 663 */ 664 @RequiresBluetoothConnectPermission 665 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) reportError(BluetoothDevice device, byte error)666 public boolean reportError(BluetoothDevice device, byte error) { 667 boolean result = false; 668 669 final IBluetoothHidDevice service = getService(); 670 if (service != null) { 671 try { 672 result = service.reportError(device, error, mAttributionSource); 673 } catch (RemoteException e) { 674 Log.e(TAG, e.toString()); 675 } 676 } else { 677 Log.w(TAG, "Proxy not attached to service"); 678 } 679 680 return result; 681 } 682 683 /** 684 * Gets the application name of the current HidDeviceService user. 685 * 686 * @return the current user name, or empty string if cannot get the name 687 * {@hide} 688 */ 689 @RequiresBluetoothConnectPermission 690 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getUserAppName()691 public String getUserAppName() { 692 final IBluetoothHidDevice service = getService(); 693 694 if (service != null) { 695 try { 696 return service.getUserAppName(mAttributionSource); 697 } catch (RemoteException e) { 698 Log.e(TAG, e.toString()); 699 } 700 } else { 701 Log.w(TAG, "Proxy not attached to service"); 702 } 703 704 return ""; 705 } 706 707 /** 708 * Initiates connection to host which is currently paired with this device. If the application 709 * is not registered, #connect(BluetoothDevice) will fail. The connection state should be 710 * tracked by the application by handling callback from Callback#onConnectionStateChanged. The 711 * connection state is not related to the return value of this method. 712 * 713 * @return true if the command is successfully sent; otherwise false. 714 */ 715 @RequiresBluetoothConnectPermission 716 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) connect(BluetoothDevice device)717 public boolean connect(BluetoothDevice device) { 718 boolean result = false; 719 720 final IBluetoothHidDevice service = getService(); 721 if (service != null) { 722 try { 723 result = service.connect(device, mAttributionSource); 724 } catch (RemoteException e) { 725 Log.e(TAG, e.toString()); 726 } 727 } else { 728 Log.w(TAG, "Proxy not attached to service"); 729 } 730 731 return result; 732 } 733 734 /** 735 * Disconnects from currently connected host. The connection state should be tracked by the 736 * application by handling callback from Callback#onConnectionStateChanged. The connection state 737 * is not related to the return value of this method. 738 * 739 * @return true if the command is successfully sent; otherwise false. 740 */ 741 @RequiresBluetoothConnectPermission 742 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) disconnect(BluetoothDevice device)743 public boolean disconnect(BluetoothDevice device) { 744 boolean result = false; 745 746 final IBluetoothHidDevice service = getService(); 747 if (service != null) { 748 try { 749 result = service.disconnect(device, mAttributionSource); 750 } catch (RemoteException e) { 751 Log.e(TAG, e.toString()); 752 } 753 } else { 754 Log.w(TAG, "Proxy not attached to service"); 755 } 756 757 return result; 758 } 759 760 /** 761 * Connects Hid Device if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} 762 * and disconnects Hid device if connectionPolicy is 763 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}. 764 * 765 * <p> The device should already be paired. 766 * Connection policy can be one of: 767 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 768 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 769 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 770 * 771 * @param device Paired bluetooth device 772 * @param connectionPolicy determines whether hid device should be connected or disconnected 773 * @return true if hid device is connected or disconnected, false otherwise 774 * 775 * @hide 776 */ 777 @SystemApi 778 @RequiresBluetoothConnectPermission 779 @RequiresPermission(allOf = { 780 android.Manifest.permission.BLUETOOTH_CONNECT, 781 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 782 }) setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)783 public boolean setConnectionPolicy(@NonNull BluetoothDevice device, 784 @ConnectionPolicy int connectionPolicy) { 785 log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 786 try { 787 final IBluetoothHidDevice service = getService(); 788 if (service != null && isEnabled() 789 && isValidDevice(device)) { 790 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 791 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 792 return false; 793 } 794 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); 795 } 796 if (service == null) Log.w(TAG, "Proxy not attached to service"); 797 return false; 798 } catch (RemoteException e) { 799 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 800 return false; 801 } 802 } 803 isEnabled()804 private boolean isEnabled() { 805 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 806 return false; 807 } 808 isValidDevice(BluetoothDevice device)809 private boolean isValidDevice(BluetoothDevice device) { 810 if (device == null) return false; 811 812 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 813 return false; 814 } 815 log(String msg)816 private static void log(String msg) { 817 if (DBG) { 818 Log.d(TAG, msg); 819 } 820 } 821 } 822