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