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