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