1 /* 2 * Copyright (C) 2011 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 static android.bluetooth.BluetoothUtils.getSyncTimeout; 20 21 import android.Manifest; 22 import android.annotation.NonNull; 23 import android.annotation.RequiresPermission; 24 import android.annotation.SdkConstant; 25 import android.annotation.SdkConstant.SdkConstantType; 26 import android.annotation.SuppressLint; 27 import android.annotation.SystemApi; 28 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 29 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; 30 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; 31 import android.content.AttributionSource; 32 import android.content.Context; 33 import android.os.IBinder; 34 import android.os.RemoteException; 35 import android.util.Log; 36 37 import com.android.modules.utils.SynchronousResultReceiver; 38 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.concurrent.TimeoutException; 42 43 /** 44 * This class provides the public APIs to control the Bluetooth Input 45 * Device Profile. 46 * 47 * <p>BluetoothHidHost is a proxy object for controlling the Bluetooth 48 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get 49 * the BluetoothHidHost proxy object. 50 * 51 * <p>Each method is protected with its appropriate permission. 52 * 53 * @hide 54 */ 55 @SystemApi 56 public final class BluetoothHidHost implements BluetoothProfile { 57 private static final String TAG = "BluetoothHidHost"; 58 private static final boolean DBG = true; 59 private static final boolean VDBG = false; 60 61 /** 62 * Intent used to broadcast the change in connection state of the Input 63 * Device profile. 64 * 65 * <p>This intent will have 3 extras: 66 * <ul> 67 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 68 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> 69 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 70 * </ul> 71 * 72 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 73 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, 74 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. 75 */ 76 @SuppressLint("ActionValue") 77 @RequiresLegacyBluetoothPermission 78 @RequiresBluetoothConnectPermission 79 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 80 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 81 public static final String ACTION_CONNECTION_STATE_CHANGED = 82 "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED"; 83 84 /** 85 * @hide 86 */ 87 @RequiresBluetoothConnectPermission 88 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 89 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 90 public static final String ACTION_PROTOCOL_MODE_CHANGED = 91 "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED"; 92 93 /** 94 * @hide 95 */ 96 @RequiresBluetoothConnectPermission 97 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 98 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 99 public static final String ACTION_HANDSHAKE = 100 "android.bluetooth.input.profile.action.HANDSHAKE"; 101 102 /** 103 * @hide 104 */ 105 @RequiresBluetoothConnectPermission 106 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 107 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 108 public static final String ACTION_REPORT = 109 "android.bluetooth.input.profile.action.REPORT"; 110 111 /** 112 * @hide 113 */ 114 @RequiresBluetoothConnectPermission 115 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 116 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 117 public static final String ACTION_VIRTUAL_UNPLUG_STATUS = 118 "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS"; 119 120 /** 121 * @hide 122 */ 123 @RequiresBluetoothConnectPermission 124 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 125 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 126 public static final String ACTION_IDLE_TIME_CHANGED = 127 "android.bluetooth.input.profile.action.IDLE_TIME_CHANGED"; 128 129 /** 130 * Return codes for the connect and disconnect Bluez / Dbus calls. 131 * 132 * @hide 133 */ 134 public static final int INPUT_DISCONNECT_FAILED_NOT_CONNECTED = 5000; 135 136 /** 137 * @hide 138 */ 139 public static final int INPUT_CONNECT_FAILED_ALREADY_CONNECTED = 5001; 140 141 /** 142 * @hide 143 */ 144 public static final int INPUT_CONNECT_FAILED_ATTEMPT_FAILED = 5002; 145 146 /** 147 * @hide 148 */ 149 public static final int INPUT_OPERATION_GENERIC_FAILURE = 5003; 150 151 /** 152 * @hide 153 */ 154 public static final int INPUT_OPERATION_SUCCESS = 5004; 155 156 /** 157 * @hide 158 */ 159 public static final int PROTOCOL_REPORT_MODE = 0; 160 161 /** 162 * @hide 163 */ 164 public static final int PROTOCOL_BOOT_MODE = 1; 165 166 /** 167 * @hide 168 */ 169 public static final int PROTOCOL_UNSUPPORTED_MODE = 255; 170 171 /* int reportType, int reportType, int bufferSize */ 172 /** 173 * @hide 174 */ 175 public static final byte REPORT_TYPE_INPUT = 1; 176 177 /** 178 * @hide 179 */ 180 public static final byte REPORT_TYPE_OUTPUT = 2; 181 182 /** 183 * @hide 184 */ 185 public static final byte REPORT_TYPE_FEATURE = 3; 186 187 /** 188 * @hide 189 */ 190 public static final int VIRTUAL_UNPLUG_STATUS_SUCCESS = 0; 191 192 /** 193 * @hide 194 */ 195 public static final int VIRTUAL_UNPLUG_STATUS_FAIL = 1; 196 197 /** 198 * @hide 199 */ 200 public static final String EXTRA_PROTOCOL_MODE = 201 "android.bluetooth.BluetoothHidHost.extra.PROTOCOL_MODE"; 202 203 /** 204 * @hide 205 */ 206 public static final String EXTRA_REPORT_TYPE = 207 "android.bluetooth.BluetoothHidHost.extra.REPORT_TYPE"; 208 209 /** 210 * @hide 211 */ 212 public static final String EXTRA_REPORT_ID = 213 "android.bluetooth.BluetoothHidHost.extra.REPORT_ID"; 214 215 /** 216 * @hide 217 */ 218 public static final String EXTRA_REPORT_BUFFER_SIZE = 219 "android.bluetooth.BluetoothHidHost.extra.REPORT_BUFFER_SIZE"; 220 221 /** 222 * @hide 223 */ 224 public static final String EXTRA_REPORT = "android.bluetooth.BluetoothHidHost.extra.REPORT"; 225 226 /** 227 * @hide 228 */ 229 public static final String EXTRA_STATUS = "android.bluetooth.BluetoothHidHost.extra.STATUS"; 230 231 /** 232 * @hide 233 */ 234 public static final String EXTRA_VIRTUAL_UNPLUG_STATUS = 235 "android.bluetooth.BluetoothHidHost.extra.VIRTUAL_UNPLUG_STATUS"; 236 237 /** 238 * @hide 239 */ 240 public static final String EXTRA_IDLE_TIME = 241 "android.bluetooth.BluetoothHidHost.extra.IDLE_TIME"; 242 243 private final BluetoothAdapter mAdapter; 244 private final AttributionSource mAttributionSource; 245 private final BluetoothProfileConnector<IBluetoothHidHost> mProfileConnector = 246 new BluetoothProfileConnector(this, BluetoothProfile.HID_HOST, 247 "BluetoothHidHost", IBluetoothHidHost.class.getName()) { 248 @Override 249 public IBluetoothHidHost getServiceInterface(IBinder service) { 250 return IBluetoothHidHost.Stub.asInterface(service); 251 } 252 }; 253 254 /** 255 * Create a BluetoothHidHost proxy object for interacting with the local 256 * Bluetooth Service which handles the InputDevice profile 257 */ BluetoothHidHost(Context context, ServiceListener listener, BluetoothAdapter adapter)258 /* package */ BluetoothHidHost(Context context, ServiceListener listener, 259 BluetoothAdapter adapter) { 260 mAdapter = adapter; 261 mAttributionSource = adapter.getAttributionSource(); 262 mProfileConnector.connect(context, listener); 263 } 264 265 /** @hide */ 266 @Override close()267 public void close() { 268 if (VDBG) log("close()"); 269 mProfileConnector.disconnect(); 270 } 271 getService()272 private IBluetoothHidHost getService() { 273 return mProfileConnector.getService(); 274 } 275 276 /** 277 * Initiate connection to a profile of the remote bluetooth device. 278 * 279 * <p> The system supports connection to multiple input devices. 280 * 281 * <p> This API returns false in scenarios like the profile on the 282 * device is already connected or Bluetooth is not turned on. 283 * When this API returns true, it is guaranteed that 284 * connection state intent for the profile will be broadcasted with 285 * the state. Users can get the connection state of the profile 286 * from this intent. 287 * 288 * @param device Remote Bluetooth Device 289 * @return false on immediate error, true otherwise 290 * @hide 291 */ 292 @RequiresBluetoothConnectPermission 293 @RequiresPermission(allOf = { 294 android.Manifest.permission.BLUETOOTH_CONNECT, 295 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 296 }) connect(BluetoothDevice device)297 public boolean connect(BluetoothDevice device) { 298 if (DBG) log("connect(" + device + ")"); 299 final IBluetoothHidHost service = getService(); 300 final boolean defaultValue = false; 301 if (service == null) { 302 Log.w(TAG, "Proxy not attached to service"); 303 if (DBG) log(Log.getStackTraceString(new Throwable())); 304 } else if (isEnabled() && isValidDevice(device)) { 305 try { 306 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 307 service.connect(device, mAttributionSource, recv); 308 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 309 } catch (RemoteException | TimeoutException e) { 310 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 311 } 312 } 313 return defaultValue; 314 } 315 316 /** 317 * Initiate disconnection from a profile 318 * 319 * <p> This API will return false in scenarios like the profile on the 320 * Bluetooth device is not in connected state etc. When this API returns, 321 * true, it is guaranteed that the connection state change 322 * intent will be broadcasted with the state. Users can get the 323 * disconnection state of the profile from this intent. 324 * 325 * <p> If the disconnection is initiated by a remote device, the state 326 * will transition from {@link #STATE_CONNECTED} to 327 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 328 * host (local) device the state will transition from 329 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 330 * state {@link #STATE_DISCONNECTED}. The transition to 331 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 332 * two scenarios. 333 * 334 * @param device Remote Bluetooth Device 335 * @return false on immediate error, true otherwise 336 * @hide 337 */ 338 @RequiresBluetoothConnectPermission 339 @RequiresPermission(allOf = { 340 android.Manifest.permission.BLUETOOTH_CONNECT, 341 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 342 }) disconnect(BluetoothDevice device)343 public boolean disconnect(BluetoothDevice device) { 344 if (DBG) log("disconnect(" + device + ")"); 345 final IBluetoothHidHost service = getService(); 346 final boolean defaultValue = false; 347 if (service == null) { 348 Log.w(TAG, "Proxy not attached to service"); 349 if (DBG) log(Log.getStackTraceString(new Throwable())); 350 } else if (isEnabled() && isValidDevice(device)) { 351 try { 352 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 353 service.disconnect(device, mAttributionSource, recv); 354 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 355 } catch (RemoteException | TimeoutException e) { 356 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 357 } 358 } 359 return defaultValue; 360 } 361 362 /** 363 * {@inheritDoc} 364 * 365 * @hide 366 */ 367 @SystemApi 368 @Override 369 @RequiresBluetoothConnectPermission 370 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getConnectedDevices()371 public @NonNull List<BluetoothDevice> getConnectedDevices() { 372 if (VDBG) log("getConnectedDevices()"); 373 final IBluetoothHidHost service = getService(); 374 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); 375 if (service == null) { 376 Log.w(TAG, "Proxy not attached to service"); 377 if (DBG) log(Log.getStackTraceString(new Throwable())); 378 } else if (isEnabled()) { 379 try { 380 final SynchronousResultReceiver<List<BluetoothDevice>> recv = 381 SynchronousResultReceiver.get(); 382 service.getConnectedDevices(mAttributionSource, recv); 383 return Attributable.setAttributionSource( 384 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), 385 mAttributionSource); 386 } catch (RemoteException | TimeoutException e) { 387 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 388 } 389 } 390 return defaultValue; 391 } 392 393 /** 394 * {@inheritDoc} 395 * 396 * @hide 397 */ 398 @Override 399 @RequiresBluetoothConnectPermission 400 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getDevicesMatchingConnectionStates(int[] states)401 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 402 if (VDBG) log("getDevicesMatchingStates()"); 403 final IBluetoothHidHost service = getService(); 404 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); 405 if (service == null) { 406 Log.w(TAG, "Proxy not attached to service"); 407 if (DBG) log(Log.getStackTraceString(new Throwable())); 408 } else if (isEnabled()) { 409 try { 410 final SynchronousResultReceiver<List<BluetoothDevice>> recv = 411 SynchronousResultReceiver.get(); 412 service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); 413 return Attributable.setAttributionSource( 414 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), 415 mAttributionSource); 416 } catch (RemoteException | TimeoutException e) { 417 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 418 } 419 } 420 return defaultValue; 421 } 422 423 /** 424 * {@inheritDoc} 425 * 426 * @hide 427 */ 428 @SystemApi 429 @Override 430 @RequiresBluetoothConnectPermission 431 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getConnectionState(@onNull BluetoothDevice device)432 public int getConnectionState(@NonNull BluetoothDevice device) { 433 if (VDBG) log("getState(" + device + ")"); 434 if (device == null) { 435 throw new IllegalArgumentException("device must not be null"); 436 } 437 final IBluetoothHidHost service = getService(); 438 final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; 439 if (service == null) { 440 Log.w(TAG, "Proxy not attached to service"); 441 if (DBG) log(Log.getStackTraceString(new Throwable())); 442 } else if (isEnabled() && isValidDevice(device)) { 443 try { 444 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); 445 service.getConnectionState(device, mAttributionSource, recv); 446 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 447 } catch (RemoteException | TimeoutException e) { 448 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 449 } 450 } 451 return defaultValue; 452 } 453 454 /** 455 * Set priority of the profile 456 * 457 * <p> The device should already be paired. 458 * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, 459 * 460 * @param device Paired bluetooth device 461 * @param priority 462 * @return true if priority is set, false on error 463 * @hide 464 */ 465 @RequiresBluetoothConnectPermission 466 @RequiresPermission(allOf = { 467 android.Manifest.permission.BLUETOOTH_CONNECT, 468 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 469 }) setPriority(BluetoothDevice device, int priority)470 public boolean setPriority(BluetoothDevice device, int priority) { 471 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 472 return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); 473 } 474 475 /** 476 * Set connection policy of the profile 477 * 478 * <p> The device should already be paired. 479 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, 480 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 481 * 482 * @param device Paired bluetooth device 483 * @param connectionPolicy is the connection policy to set to for this profile 484 * @return true if connectionPolicy is set, false on error 485 * @hide 486 */ 487 @SystemApi 488 @RequiresBluetoothConnectPermission 489 @RequiresPermission(allOf = { 490 android.Manifest.permission.BLUETOOTH_CONNECT, 491 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 492 }) setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)493 public boolean setConnectionPolicy(@NonNull BluetoothDevice device, 494 @ConnectionPolicy int connectionPolicy) { 495 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 496 if (device == null) { 497 throw new IllegalArgumentException("device must not be null"); 498 } 499 final IBluetoothHidHost service = getService(); 500 final boolean defaultValue = false; 501 if (service == null) { 502 Log.w(TAG, "Proxy not attached to service"); 503 if (DBG) log(Log.getStackTraceString(new Throwable())); 504 } else if (isEnabled() && isValidDevice(device) 505 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 506 || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { 507 try { 508 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 509 service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); 510 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 511 } catch (RemoteException | TimeoutException e) { 512 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 513 } 514 } 515 return defaultValue; 516 } 517 518 /** 519 * Get the priority of the profile. 520 * 521 * <p> The priority can be any of: 522 * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 523 * 524 * @param device Bluetooth device 525 * @return priority of the device 526 * @hide 527 */ 528 @RequiresBluetoothConnectPermission 529 @RequiresPermission(allOf = { 530 android.Manifest.permission.BLUETOOTH_CONNECT, 531 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 532 }) getPriority(BluetoothDevice device)533 public int getPriority(BluetoothDevice device) { 534 if (VDBG) log("getPriority(" + device + ")"); 535 return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); 536 } 537 538 /** 539 * Get the connection policy of the profile. 540 * 541 * <p> The connection policy can be any of: 542 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, 543 * {@link #CONNECTION_POLICY_UNKNOWN} 544 * 545 * @param device Bluetooth device 546 * @return connection policy of the device 547 * @hide 548 */ 549 @SystemApi 550 @RequiresBluetoothConnectPermission 551 @RequiresPermission(allOf = { 552 android.Manifest.permission.BLUETOOTH_CONNECT, 553 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 554 }) getConnectionPolicy(@onNull BluetoothDevice device)555 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { 556 if (VDBG) log("getConnectionPolicy(" + device + ")"); 557 if (device == null) { 558 throw new IllegalArgumentException("device must not be null"); 559 } 560 final IBluetoothHidHost service = getService(); 561 final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 562 if (service == null) { 563 Log.w(TAG, "Proxy not attached to service"); 564 if (DBG) log(Log.getStackTraceString(new Throwable())); 565 } else if (isEnabled() && isValidDevice(device)) { 566 try { 567 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); 568 service.getConnectionPolicy(device, mAttributionSource, recv); 569 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 570 } catch (RemoteException | TimeoutException e) { 571 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 572 } 573 } 574 return defaultValue; 575 } 576 isEnabled()577 private boolean isEnabled() { 578 return mAdapter.getState() == BluetoothAdapter.STATE_ON; 579 } 580 isValidDevice(BluetoothDevice device)581 private static boolean isValidDevice(BluetoothDevice device) { 582 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 583 } 584 585 /** 586 * Initiate virtual unplug for a HID input device. 587 * 588 * @param device Remote Bluetooth Device 589 * @return false on immediate error, true otherwise 590 * @hide 591 */ 592 @RequiresLegacyBluetoothAdminPermission 593 @RequiresBluetoothConnectPermission 594 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) virtualUnplug(BluetoothDevice device)595 public boolean virtualUnplug(BluetoothDevice device) { 596 if (DBG) log("virtualUnplug(" + device + ")"); 597 final IBluetoothHidHost service = getService(); 598 final boolean defaultValue = false; 599 if (service == null) { 600 Log.w(TAG, "Proxy not attached to service"); 601 if (DBG) log(Log.getStackTraceString(new Throwable())); 602 } else if (isEnabled() && isValidDevice(device)) { 603 try { 604 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 605 service.virtualUnplug(device, mAttributionSource, recv); 606 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 607 } catch (RemoteException | TimeoutException e) { 608 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 609 } 610 } 611 return defaultValue; 612 } 613 614 /** 615 * Send Get_Protocol_Mode command to the connected HID input device. 616 * 617 * @param device Remote Bluetooth Device 618 * @return false on immediate error, true otherwise 619 * @hide 620 */ 621 @RequiresLegacyBluetoothAdminPermission 622 @RequiresBluetoothConnectPermission 623 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getProtocolMode(BluetoothDevice device)624 public boolean getProtocolMode(BluetoothDevice device) { 625 if (VDBG) log("getProtocolMode(" + device + ")"); 626 final IBluetoothHidHost service = getService(); 627 final boolean defaultValue = false; 628 if (service == null) { 629 Log.w(TAG, "Proxy not attached to service"); 630 if (DBG) log(Log.getStackTraceString(new Throwable())); 631 } else if (isEnabled() && isValidDevice(device)) { 632 try { 633 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 634 service.getProtocolMode(device, mAttributionSource, recv); 635 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 636 } catch (RemoteException | TimeoutException e) { 637 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 638 } 639 } 640 return defaultValue; 641 } 642 643 /** 644 * Send Set_Protocol_Mode command to the connected HID input device. 645 * 646 * @param device Remote Bluetooth Device 647 * @return false on immediate error, true otherwise 648 * @hide 649 */ 650 @RequiresLegacyBluetoothAdminPermission 651 @RequiresBluetoothConnectPermission 652 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) setProtocolMode(BluetoothDevice device, int protocolMode)653 public boolean setProtocolMode(BluetoothDevice device, int protocolMode) { 654 if (DBG) log("setProtocolMode(" + device + ")"); 655 final IBluetoothHidHost service = getService(); 656 final boolean defaultValue = false; 657 if (service == null) { 658 Log.w(TAG, "Proxy not attached to service"); 659 if (DBG) log(Log.getStackTraceString(new Throwable())); 660 } else if (isEnabled() && isValidDevice(device)) { 661 try { 662 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 663 service.setProtocolMode(device, protocolMode, mAttributionSource, recv); 664 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 665 } catch (RemoteException | TimeoutException e) { 666 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 667 } 668 } 669 return defaultValue; 670 } 671 672 /** 673 * Send Get_Report command to the connected HID input device. 674 * 675 * @param device Remote Bluetooth Device 676 * @param reportType Report type 677 * @param reportId Report ID 678 * @param bufferSize Report receiving buffer size 679 * @return false on immediate error, true otherwise 680 * @hide 681 */ 682 @RequiresLegacyBluetoothAdminPermission 683 @RequiresBluetoothConnectPermission 684 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize)685 public boolean getReport(BluetoothDevice device, byte reportType, byte reportId, 686 int bufferSize) { 687 if (VDBG) { 688 log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId 689 + "bufferSize=" + bufferSize); 690 } 691 final IBluetoothHidHost service = getService(); 692 final boolean defaultValue = false; 693 if (service == null) { 694 Log.w(TAG, "Proxy not attached to service"); 695 if (DBG) log(Log.getStackTraceString(new Throwable())); 696 } else if (isEnabled() && isValidDevice(device)) { 697 try { 698 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 699 service.getReport(device, reportType, reportId, bufferSize, mAttributionSource, 700 recv); 701 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 702 } catch (RemoteException | TimeoutException e) { 703 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 704 } 705 } 706 return defaultValue; 707 } 708 709 /** 710 * Send Set_Report command to the connected HID input device. 711 * 712 * @param device Remote Bluetooth Device 713 * @param reportType Report type 714 * @param report Report receiving buffer size 715 * @return false on immediate error, true otherwise 716 * @hide 717 */ 718 @RequiresLegacyBluetoothAdminPermission 719 @RequiresBluetoothConnectPermission 720 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) setReport(BluetoothDevice device, byte reportType, String report)721 public boolean setReport(BluetoothDevice device, byte reportType, String report) { 722 if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report); 723 final IBluetoothHidHost service = getService(); 724 final boolean defaultValue = false; 725 if (service == null) { 726 Log.w(TAG, "Proxy not attached to service"); 727 if (DBG) log(Log.getStackTraceString(new Throwable())); 728 } else if (isEnabled() && isValidDevice(device)) { 729 try { 730 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 731 service.setReport(device, reportType, report, mAttributionSource, recv); 732 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 733 } catch (RemoteException | TimeoutException e) { 734 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 735 } 736 } 737 return defaultValue; 738 } 739 740 /** 741 * Send Send_Data command to the connected HID input device. 742 * 743 * @param device Remote Bluetooth Device 744 * @param report Report to send 745 * @return false on immediate error, true otherwise 746 * @hide 747 */ 748 @RequiresLegacyBluetoothAdminPermission 749 @RequiresBluetoothConnectPermission 750 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) sendData(BluetoothDevice device, String report)751 public boolean sendData(BluetoothDevice device, String report) { 752 if (DBG) log("sendData(" + device + "), report=" + report); 753 final IBluetoothHidHost service = getService(); 754 final boolean defaultValue = false; 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() && isValidDevice(device)) { 759 try { 760 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 761 service.sendData(device, report, mAttributionSource, recv); 762 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 763 } catch (RemoteException | TimeoutException e) { 764 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 765 } 766 } 767 return defaultValue; 768 } 769 770 /** 771 * Send Get_Idle_Time command to the connected HID input device. 772 * 773 * @param device Remote Bluetooth Device 774 * @return false on immediate error, true otherwise 775 * @hide 776 */ 777 @RequiresLegacyBluetoothAdminPermission 778 @RequiresBluetoothConnectPermission 779 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getIdleTime(BluetoothDevice device)780 public boolean getIdleTime(BluetoothDevice device) { 781 if (DBG) log("getIdletime(" + device + ")"); 782 final IBluetoothHidHost service = getService(); 783 final boolean defaultValue = false; 784 if (service == null) { 785 Log.w(TAG, "Proxy not attached to service"); 786 if (DBG) log(Log.getStackTraceString(new Throwable())); 787 } else if (isEnabled() && isValidDevice(device)) { 788 try { 789 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 790 service.getIdleTime(device, mAttributionSource, recv); 791 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 792 } catch (RemoteException | TimeoutException e) { 793 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 794 } 795 } 796 return defaultValue; 797 } 798 799 /** 800 * Send Set_Idle_Time command to the connected HID input device. 801 * 802 * @param device Remote Bluetooth Device 803 * @param idleTime Idle time to be set on HID Device 804 * @return false on immediate error, true otherwise 805 * @hide 806 */ 807 @RequiresLegacyBluetoothAdminPermission 808 @RequiresBluetoothConnectPermission 809 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) setIdleTime(BluetoothDevice device, byte idleTime)810 public boolean setIdleTime(BluetoothDevice device, byte idleTime) { 811 if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime); 812 final IBluetoothHidHost service = getService(); 813 final boolean defaultValue = false; 814 if (service == null) { 815 Log.w(TAG, "Proxy not attached to service"); 816 if (DBG) log(Log.getStackTraceString(new Throwable())); 817 } else if (isEnabled() && isValidDevice(device)) { 818 try { 819 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 820 service.setIdleTime(device, idleTime, mAttributionSource, recv); 821 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 822 } catch (RemoteException | TimeoutException e) { 823 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 824 } 825 } 826 return defaultValue; 827 } 828 log(String msg)829 private static void log(String msg) { 830 Log.d(TAG, msg); 831 } 832 } 833