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