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