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