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