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