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