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 if (!mContext.bindService(new Intent(IBluetoothInputDevice.class.getName()), mConnection, 0)) { 210 Log.e(TAG, "Could not bind to Bluetooth HID Service"); 211 } 212 } 213 } catch (Exception re) { 214 Log.e(TAG,"",re); 215 } 216 } 217 } 218 } 219 }; 220 221 /** 222 * Create a BluetoothInputDevice proxy object for interacting with the local 223 * Bluetooth Service which handles the InputDevice profile 224 * 225 */ 226 /*package*/ BluetoothInputDevice(Context context, ServiceListener l) { 227 mContext = context; 228 mServiceListener = l; 229 mAdapter = BluetoothAdapter.getDefaultAdapter(); 230 231 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 232 if (mgr != null) { 233 try { 234 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); 235 } catch (RemoteException e) { 236 Log.e(TAG,"",e); 237 } 238 } 239 240 if (!context.bindService(new Intent(IBluetoothInputDevice.class.getName()), 241 mConnection, 0)) { 242 Log.e(TAG, "Could not bind to Bluetooth HID Service"); 243 } 244 } 245 246 /*package*/ void close() { 247 if (VDBG) log("close()"); 248 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 249 if (mgr != null) { 250 try { 251 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); 252 } catch (Exception e) { 253 Log.e(TAG,"",e); 254 } 255 } 256 257 synchronized (mConnection) { 258 if (mService != null) { 259 try { 260 mService = null; 261 mContext.unbindService(mConnection); 262 } catch (Exception re) { 263 Log.e(TAG,"",re); 264 } 265 } 266 } 267 mServiceListener = null; 268 } 269 270 /** 271 * Initiate connection to a profile of the remote bluetooth device. 272 * 273 * <p> The system supports connection to multiple input devices. 274 * 275 * <p> This API returns false in scenarios like the profile on the 276 * device is already connected or Bluetooth is not turned on. 277 * When this API returns true, it is guaranteed that 278 * connection state intent for the profile will be broadcasted with 279 * the state. Users can get the connection state of the profile 280 * from this intent. 281 * 282 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 283 * permission. 284 * 285 * @param device Remote Bluetooth Device 286 * @return false on immediate error, 287 * true otherwise 288 * @hide 289 */ 290 public boolean connect(BluetoothDevice device) { 291 if (DBG) log("connect(" + device + ")"); 292 if (mService != null && isEnabled() && isValidDevice(device)) { 293 try { 294 return mService.connect(device); 295 } catch (RemoteException e) { 296 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 297 return false; 298 } 299 } 300 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 301 return false; 302 } 303 304 /** 305 * Initiate disconnection from a profile 306 * 307 * <p> This API will return false in scenarios like the profile on the 308 * Bluetooth device is not in connected state etc. When this API returns, 309 * true, it is guaranteed that the connection state change 310 * intent will be broadcasted with the state. Users can get the 311 * disconnection state of the profile from this intent. 312 * 313 * <p> If the disconnection is initiated by a remote device, the state 314 * will transition from {@link #STATE_CONNECTED} to 315 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 316 * host (local) device the state will transition from 317 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 318 * state {@link #STATE_DISCONNECTED}. The transition to 319 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 320 * two scenarios. 321 * 322 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 323 * permission. 324 * 325 * @param device Remote Bluetooth Device 326 * @return false on immediate error, 327 * true otherwise 328 * @hide 329 */ 330 public boolean disconnect(BluetoothDevice device) { 331 if (DBG) log("disconnect(" + device + ")"); 332 if (mService != null && isEnabled() && isValidDevice(device)) { 333 try { 334 return mService.disconnect(device); 335 } catch (RemoteException e) { 336 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 337 return false; 338 } 339 } 340 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 341 return false; 342 } 343 344 /** 345 * {@inheritDoc} 346 */ 347 public List<BluetoothDevice> getConnectedDevices() { 348 if (VDBG) log("getConnectedDevices()"); 349 if (mService != null && isEnabled()) { 350 try { 351 return mService.getConnectedDevices(); 352 } catch (RemoteException e) { 353 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 354 return new ArrayList<BluetoothDevice>(); 355 } 356 } 357 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 358 return new ArrayList<BluetoothDevice>(); 359 } 360 361 /** 362 * {@inheritDoc} 363 */ 364 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 365 if (VDBG) log("getDevicesMatchingStates()"); 366 if (mService != null && isEnabled()) { 367 try { 368 return mService.getDevicesMatchingConnectionStates(states); 369 } catch (RemoteException e) { 370 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 371 return new ArrayList<BluetoothDevice>(); 372 } 373 } 374 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 375 return new ArrayList<BluetoothDevice>(); 376 } 377 378 /** 379 * {@inheritDoc} 380 */ 381 public int getConnectionState(BluetoothDevice device) { 382 if (VDBG) log("getState(" + device + ")"); 383 if (mService != null && isEnabled() && isValidDevice(device)) { 384 try { 385 return mService.getConnectionState(device); 386 } catch (RemoteException e) { 387 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 388 return BluetoothProfile.STATE_DISCONNECTED; 389 } 390 } 391 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 392 return BluetoothProfile.STATE_DISCONNECTED; 393 } 394 395 /** 396 * Set priority of the profile 397 * 398 * <p> The device should already be paired. 399 * Priority can be one of {@link #PRIORITY_ON} or 400 * {@link #PRIORITY_OFF}, 401 * 402 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} 403 * permission. 404 * 405 * @param device Paired bluetooth device 406 * @param priority 407 * @return true if priority is set, false on error 408 * @hide 409 */ 410 public boolean setPriority(BluetoothDevice device, int priority) { 411 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 412 if (mService != null && isEnabled() && isValidDevice(device)) { 413 if (priority != BluetoothProfile.PRIORITY_OFF && 414 priority != BluetoothProfile.PRIORITY_ON) { 415 return false; 416 } 417 try { 418 return mService.setPriority(device, priority); 419 } catch (RemoteException e) { 420 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 421 return false; 422 } 423 } 424 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 425 return false; 426 } 427 428 /** 429 * Get the priority of the profile. 430 * 431 * <p> The priority can be any of: 432 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, 433 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 434 * 435 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 436 * 437 * @param device Bluetooth device 438 * @return priority of the device 439 * @hide 440 */ 441 public int getPriority(BluetoothDevice device) { 442 if (VDBG) log("getPriority(" + device + ")"); 443 if (mService != null && isEnabled() && isValidDevice(device)) { 444 try { 445 return mService.getPriority(device); 446 } catch (RemoteException e) { 447 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 448 return BluetoothProfile.PRIORITY_OFF; 449 } 450 } 451 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 452 return BluetoothProfile.PRIORITY_OFF; 453 } 454 455 private ServiceConnection mConnection = new ServiceConnection() { 456 public void onServiceConnected(ComponentName className, IBinder service) { 457 if (DBG) Log.d(TAG, "Proxy object connected"); 458 mService = IBluetoothInputDevice.Stub.asInterface(service); 459 460 if (mServiceListener != null) { 461 mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE, BluetoothInputDevice.this); 462 } 463 } 464 public void onServiceDisconnected(ComponentName className) { 465 if (DBG) Log.d(TAG, "Proxy object disconnected"); 466 mService = null; 467 if (mServiceListener != null) { 468 mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_DEVICE); 469 } 470 } 471 }; 472 473 private boolean isEnabled() { 474 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 475 return false; 476 } 477 478 private boolean isValidDevice(BluetoothDevice device) { 479 if (device == null) return false; 480 481 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 482 return false; 483 } 484 485 486 /** 487 * Initiate virtual unplug for a HID input device. 488 * 489 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 490 * 491 * @param device Remote Bluetooth Device 492 * @return false on immediate error, 493 * true otherwise 494 * @hide 495 */ 496 public boolean virtualUnplug(BluetoothDevice device) { 497 if (DBG) log("virtualUnplug(" + device + ")"); 498 if (mService != null && isEnabled() && isValidDevice(device)) { 499 try { 500 return mService.virtualUnplug(device); 501 } catch (RemoteException e) { 502 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 503 return false; 504 } 505 } 506 507 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 508 return false; 509 510 } 511 512 /** 513 * Send Get_Protocol_Mode command to the connected HID input device. 514 * 515 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 516 * 517 * @param device Remote Bluetooth Device 518 * @return false on immediate error, 519 *true otherwise 520 * @hide 521 */ 522 public boolean getProtocolMode(BluetoothDevice device) { 523 if (VDBG) log("getProtocolMode(" + device + ")"); 524 if (mService != null && isEnabled() && isValidDevice(device)) { 525 try { 526 return mService.getProtocolMode(device); 527 } catch (RemoteException e) { 528 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 529 return false; 530 } 531 } 532 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 533 return false; 534 } 535 536 /** 537 * Send Set_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 */ 546 public boolean setProtocolMode(BluetoothDevice device, int protocolMode) { 547 if (DBG) log("setProtocolMode(" + device + ")"); 548 if (mService != null && isEnabled() && isValidDevice(device)) { 549 try { 550 return mService.setProtocolMode(device, protocolMode); 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 Get_Report 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 * @param reportType Report type 567 * @param reportId Report ID 568 * @param bufferSize Report receiving buffer size 569 * @return false on immediate error, 570 * true otherwise 571 * @hide 572 */ 573 public boolean getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize) { 574 if (VDBG) log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId + "bufferSize=" + bufferSize); 575 if (mService != null && isEnabled() && isValidDevice(device)) { 576 try { 577 return mService.getReport(device, reportType, reportId, bufferSize); 578 } catch (RemoteException e) { 579 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 580 return false; 581 } 582 } 583 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 584 return false; 585 } 586 587 /** 588 * Send Set_Report command to the connected HID input device. 589 * 590 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 591 * 592 * @param device Remote Bluetooth Device 593 * @param reportType Report type 594 * @param report Report receiving buffer size 595 * @return false on immediate error, 596 * true otherwise 597 * @hide 598 */ 599 public boolean setReport(BluetoothDevice device, byte reportType, String report) { 600 if (DBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report); 601 if (mService != null && isEnabled() && isValidDevice(device)) { 602 try { 603 return mService.setReport(device, reportType, report); 604 } catch (RemoteException e) { 605 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 606 return false; 607 } 608 } 609 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 610 return false; 611 } 612 613 /** 614 * Send Send_Data command to the connected HID input device. 615 * 616 * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. 617 * 618 * @param device Remote Bluetooth Device 619 * @param data Data to send 620 * @return false on immediate error, 621 * true otherwise 622 * @hide 623 */ 624 public boolean sendData(BluetoothDevice device, String report) { 625 if (DBG) log("sendData(" + device + "), report=" + report); 626 if (mService != null && isEnabled() && isValidDevice(device)) { 627 try { 628 return mService.sendData(device, 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 private static void log(String msg) { 638 Log.d(TAG, msg); 639 } 640 } 641