1 /* 2 * Copyright (C) 2013 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.content.Context; 20 import android.os.ParcelUuid; 21 import android.os.RemoteException; 22 import android.util.Log; 23 24 import java.util.ArrayList; 25 import java.util.List; 26 import java.util.UUID; 27 28 /** 29 * Public API for the Bluetooth GATT Profile. 30 * 31 * <p>This class provides Bluetooth GATT functionality to enable communication 32 * with Bluetooth Smart or Smart Ready devices. 33 * 34 * <p>To connect to a remote peripheral device, create a {@link BluetoothGattCallback} 35 * and call {@link BluetoothDevice#connectGatt} to get a instance of this class. 36 * GATT capable devices can be discovered using the Bluetooth device discovery or BLE 37 * scan process. 38 */ 39 public final class BluetoothGatt implements BluetoothProfile { 40 private static final String TAG = "BluetoothGatt"; 41 private static final boolean DBG = true; 42 private static final boolean VDBG = false; 43 44 private final Context mContext; 45 private IBluetoothGatt mService; 46 private BluetoothGattCallback mCallback; 47 private int mClientIf; 48 private boolean mAuthRetry = false; 49 private BluetoothDevice mDevice; 50 private boolean mAutoConnect; 51 private int mConnState; 52 private final Object mStateLock = new Object(); 53 private Boolean mDeviceBusy = false; 54 private int mTransport; 55 56 private static final int CONN_STATE_IDLE = 0; 57 private static final int CONN_STATE_CONNECTING = 1; 58 private static final int CONN_STATE_CONNECTED = 2; 59 private static final int CONN_STATE_DISCONNECTING = 3; 60 private static final int CONN_STATE_CLOSED = 4; 61 62 private List<BluetoothGattService> mServices; 63 64 /** A GATT operation completed successfully */ 65 public static final int GATT_SUCCESS = 0; 66 67 /** GATT read operation is not permitted */ 68 public static final int GATT_READ_NOT_PERMITTED = 0x2; 69 70 /** GATT write operation is not permitted */ 71 public static final int GATT_WRITE_NOT_PERMITTED = 0x3; 72 73 /** Insufficient authentication for a given operation */ 74 public static final int GATT_INSUFFICIENT_AUTHENTICATION = 0x5; 75 76 /** The given request is not supported */ 77 public static final int GATT_REQUEST_NOT_SUPPORTED = 0x6; 78 79 /** Insufficient encryption for a given operation */ 80 public static final int GATT_INSUFFICIENT_ENCRYPTION = 0xf; 81 82 /** A read or write operation was requested with an invalid offset */ 83 public static final int GATT_INVALID_OFFSET = 0x7; 84 85 /** A write operation exceeds the maximum length of the attribute */ 86 public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 0xd; 87 88 /** A remote device connection is congested. */ 89 public static final int GATT_CONNECTION_CONGESTED = 0x8f; 90 91 /** A GATT operation failed, errors other than the above */ 92 public static final int GATT_FAILURE = 0x101; 93 94 /** 95 * Connection paramter update - Use the connection paramters recommended by the 96 * Bluetooth SIG. This is the default value if no connection parameter update 97 * is requested. 98 */ 99 public static final int CONNECTION_PRIORITY_BALANCED = 0; 100 101 /** 102 * Connection paramter update - Request a high priority, low latency connection. 103 * An application should only request high priority connection paramters to transfer 104 * large amounts of data over LE quickly. Once the transfer is complete, the application 105 * should request {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED} connectoin parameters 106 * to reduce energy use. 107 */ 108 public static final int CONNECTION_PRIORITY_HIGH = 1; 109 110 /** Connection paramter update - Request low power, reduced data rate connection parameters. */ 111 public static final int CONNECTION_PRIORITY_LOW_POWER = 2; 112 113 /** 114 * No authentication required. 115 * @hide 116 */ 117 /*package*/ static final int AUTHENTICATION_NONE = 0; 118 119 /** 120 * Authentication requested; no man-in-the-middle protection required. 121 * @hide 122 */ 123 /*package*/ static final int AUTHENTICATION_NO_MITM = 1; 124 125 /** 126 * Authentication with man-in-the-middle protection requested. 127 * @hide 128 */ 129 /*package*/ static final int AUTHENTICATION_MITM = 2; 130 131 /** 132 * Bluetooth GATT callbacks. Overrides the default BluetoothGattCallback implementation. 133 */ 134 private final IBluetoothGattCallback mBluetoothGattCallback = 135 new BluetoothGattCallbackWrapper() { 136 /** 137 * Application interface registered - app is ready to go 138 * @hide 139 */ 140 public void onClientRegistered(int status, int clientIf) { 141 if (DBG) Log.d(TAG, "onClientRegistered() - status=" + status 142 + " clientIf=" + clientIf); 143 if (VDBG) { 144 synchronized(mStateLock) { 145 if (mConnState != CONN_STATE_CONNECTING) { 146 Log.e(TAG, "Bad connection state: " + mConnState); 147 } 148 } 149 } 150 mClientIf = clientIf; 151 if (status != GATT_SUCCESS) { 152 mCallback.onConnectionStateChange(BluetoothGatt.this, GATT_FAILURE, 153 BluetoothProfile.STATE_DISCONNECTED); 154 synchronized(mStateLock) { 155 mConnState = CONN_STATE_IDLE; 156 } 157 return; 158 } 159 try { 160 mService.clientConnect(mClientIf, mDevice.getAddress(), 161 !mAutoConnect, mTransport); // autoConnect is inverse of "isDirect" 162 } catch (RemoteException e) { 163 Log.e(TAG,"",e); 164 } 165 } 166 167 /** 168 * Client connection state changed 169 * @hide 170 */ 171 public void onClientConnectionState(int status, int clientIf, 172 boolean connected, String address) { 173 if (DBG) Log.d(TAG, "onClientConnectionState() - status=" + status 174 + " clientIf=" + clientIf + " device=" + address); 175 if (!address.equals(mDevice.getAddress())) { 176 return; 177 } 178 int profileState = connected ? BluetoothProfile.STATE_CONNECTED : 179 BluetoothProfile.STATE_DISCONNECTED; 180 try { 181 mCallback.onConnectionStateChange(BluetoothGatt.this, status, profileState); 182 } catch (Exception ex) { 183 Log.w(TAG, "Unhandled exception in callback", ex); 184 } 185 186 synchronized(mStateLock) { 187 if (connected) { 188 mConnState = CONN_STATE_CONNECTED; 189 } else { 190 mConnState = CONN_STATE_IDLE; 191 } 192 } 193 194 synchronized(mDeviceBusy) { 195 mDeviceBusy = false; 196 } 197 } 198 199 /** 200 * A new GATT service has been discovered. 201 * The service is added to the internal list and the search 202 * continues. 203 * @hide 204 */ 205 public void onGetService(String address, int srvcType, 206 int srvcInstId, ParcelUuid srvcUuid) { 207 if (VDBG) Log.d(TAG, "onGetService() - Device=" + address + " UUID=" + srvcUuid); 208 if (!address.equals(mDevice.getAddress())) { 209 return; 210 } 211 mServices.add(new BluetoothGattService(mDevice, srvcUuid.getUuid(), 212 srvcInstId, srvcType)); 213 } 214 215 /** 216 * An included service has been found durig GATT discovery. 217 * The included service is added to the respective parent. 218 * @hide 219 */ 220 public void onGetIncludedService(String address, int srvcType, 221 int srvcInstId, ParcelUuid srvcUuid, 222 int inclSrvcType, int inclSrvcInstId, 223 ParcelUuid inclSrvcUuid) { 224 if (VDBG) Log.d(TAG, "onGetIncludedService() - Device=" + address 225 + " UUID=" + srvcUuid + " Included=" + inclSrvcUuid); 226 227 if (!address.equals(mDevice.getAddress())) { 228 return; 229 } 230 BluetoothGattService service = getService(mDevice, 231 srvcUuid.getUuid(), srvcInstId, srvcType); 232 BluetoothGattService includedService = getService(mDevice, 233 inclSrvcUuid.getUuid(), inclSrvcInstId, inclSrvcType); 234 235 if (service != null && includedService != null) { 236 service.addIncludedService(includedService); 237 } 238 } 239 240 /** 241 * A new GATT characteristic has been discovered. 242 * Add the new characteristic to the relevant service and continue 243 * the remote device inspection. 244 * @hide 245 */ 246 public void onGetCharacteristic(String address, int srvcType, 247 int srvcInstId, ParcelUuid srvcUuid, 248 int charInstId, ParcelUuid charUuid, 249 int charProps) { 250 if (VDBG) Log.d(TAG, "onGetCharacteristic() - Device=" + address + " UUID=" + 251 charUuid); 252 253 if (!address.equals(mDevice.getAddress())) { 254 return; 255 } 256 BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(), 257 srvcInstId, srvcType); 258 if (service != null) { 259 service.addCharacteristic(new BluetoothGattCharacteristic( 260 service, charUuid.getUuid(), charInstId, charProps, 0)); 261 } 262 } 263 264 /** 265 * A new GATT descriptor has been discovered. 266 * Finally, add the descriptor to the related characteristic. 267 * This should conclude the remote device update. 268 * @hide 269 */ 270 public void onGetDescriptor(String address, int srvcType, 271 int srvcInstId, ParcelUuid srvcUuid, 272 int charInstId, ParcelUuid charUuid, 273 int descrInstId, ParcelUuid descUuid) { 274 if (VDBG) Log.d(TAG, "onGetDescriptor() - Device=" + address + " UUID=" + descUuid); 275 276 if (!address.equals(mDevice.getAddress())) { 277 return; 278 } 279 BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(), 280 srvcInstId, srvcType); 281 if (service == null) return; 282 283 BluetoothGattCharacteristic characteristic = service.getCharacteristic( 284 charUuid.getUuid(), charInstId); 285 if (characteristic == null) return; 286 287 characteristic.addDescriptor(new BluetoothGattDescriptor( 288 characteristic, descUuid.getUuid(), descrInstId, 0)); 289 } 290 291 /** 292 * Remote search has been completed. 293 * The internal object structure should now reflect the state 294 * of the remote device database. Let the application know that 295 * we are done at this point. 296 * @hide 297 */ 298 public void onSearchComplete(String address, int status) { 299 if (DBG) Log.d(TAG, "onSearchComplete() = Device=" + address + " Status=" + status); 300 if (!address.equals(mDevice.getAddress())) { 301 return; 302 } 303 try { 304 mCallback.onServicesDiscovered(BluetoothGatt.this, status); 305 } catch (Exception ex) { 306 Log.w(TAG, "Unhandled exception in callback", ex); 307 } 308 } 309 310 /** 311 * Remote characteristic has been read. 312 * Updates the internal value. 313 * @hide 314 */ 315 public void onCharacteristicRead(String address, int status, int srvcType, 316 int srvcInstId, ParcelUuid srvcUuid, 317 int charInstId, ParcelUuid charUuid, byte[] value) { 318 if (VDBG) Log.d(TAG, "onCharacteristicRead() - Device=" + address 319 + " UUID=" + charUuid + " Status=" + status); 320 321 if (!address.equals(mDevice.getAddress())) { 322 return; 323 } 324 325 synchronized(mDeviceBusy) { 326 mDeviceBusy = false; 327 } 328 329 if ((status == GATT_INSUFFICIENT_AUTHENTICATION 330 || status == GATT_INSUFFICIENT_ENCRYPTION) 331 && mAuthRetry == false) { 332 try { 333 mAuthRetry = true; 334 mService.readCharacteristic(mClientIf, address, 335 srvcType, srvcInstId, srvcUuid, 336 charInstId, charUuid, AUTHENTICATION_MITM); 337 return; 338 } catch (RemoteException e) { 339 Log.e(TAG,"",e); 340 } 341 } 342 343 mAuthRetry = false; 344 345 BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(), 346 srvcInstId, srvcType); 347 if (service == null) return; 348 349 BluetoothGattCharacteristic characteristic = service.getCharacteristic( 350 charUuid.getUuid(), charInstId); 351 if (characteristic == null) return; 352 353 if (status == 0) characteristic.setValue(value); 354 355 try { 356 mCallback.onCharacteristicRead(BluetoothGatt.this, characteristic, status); 357 } catch (Exception ex) { 358 Log.w(TAG, "Unhandled exception in callback", ex); 359 } 360 } 361 362 /** 363 * Characteristic has been written to the remote device. 364 * Let the app know how we did... 365 * @hide 366 */ 367 public void onCharacteristicWrite(String address, int status, int srvcType, 368 int srvcInstId, ParcelUuid srvcUuid, 369 int charInstId, ParcelUuid charUuid) { 370 if (VDBG) Log.d(TAG, "onCharacteristicWrite() - Device=" + address 371 + " UUID=" + charUuid + " Status=" + status); 372 373 if (!address.equals(mDevice.getAddress())) { 374 return; 375 } 376 377 synchronized(mDeviceBusy) { 378 mDeviceBusy = false; 379 } 380 381 BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(), 382 srvcInstId, srvcType); 383 if (service == null) return; 384 385 BluetoothGattCharacteristic characteristic = service.getCharacteristic( 386 charUuid.getUuid(), charInstId); 387 if (characteristic == null) return; 388 389 if ((status == GATT_INSUFFICIENT_AUTHENTICATION 390 || status == GATT_INSUFFICIENT_ENCRYPTION) 391 && mAuthRetry == false) { 392 try { 393 mAuthRetry = true; 394 mService.writeCharacteristic(mClientIf, address, 395 srvcType, srvcInstId, srvcUuid, charInstId, charUuid, 396 characteristic.getWriteType(), AUTHENTICATION_MITM, 397 characteristic.getValue()); 398 return; 399 } catch (RemoteException e) { 400 Log.e(TAG,"",e); 401 } 402 } 403 404 mAuthRetry = false; 405 406 try { 407 mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic, status); 408 } catch (Exception ex) { 409 Log.w(TAG, "Unhandled exception in callback", ex); 410 } 411 } 412 413 /** 414 * Remote characteristic has been updated. 415 * Updates the internal value. 416 * @hide 417 */ 418 public void onNotify(String address, int srvcType, 419 int srvcInstId, ParcelUuid srvcUuid, 420 int charInstId, ParcelUuid charUuid, 421 byte[] value) { 422 if (VDBG) Log.d(TAG, "onNotify() - Device=" + address + " UUID=" + charUuid); 423 424 if (!address.equals(mDevice.getAddress())) { 425 return; 426 } 427 BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(), 428 srvcInstId, srvcType); 429 if (service == null) return; 430 431 BluetoothGattCharacteristic characteristic = service.getCharacteristic( 432 charUuid.getUuid(), charInstId); 433 if (characteristic == null) return; 434 435 characteristic.setValue(value); 436 437 try { 438 mCallback.onCharacteristicChanged(BluetoothGatt.this, characteristic); 439 } catch (Exception ex) { 440 Log.w(TAG, "Unhandled exception in callback", ex); 441 } 442 } 443 444 /** 445 * Descriptor has been read. 446 * @hide 447 */ 448 public void onDescriptorRead(String address, int status, int srvcType, 449 int srvcInstId, ParcelUuid srvcUuid, 450 int charInstId, ParcelUuid charUuid, 451 int descrInstId, ParcelUuid descrUuid, 452 byte[] value) { 453 if (VDBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " UUID=" + charUuid); 454 455 if (!address.equals(mDevice.getAddress())) { 456 return; 457 } 458 459 synchronized(mDeviceBusy) { 460 mDeviceBusy = false; 461 } 462 463 BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(), 464 srvcInstId, srvcType); 465 if (service == null) return; 466 467 BluetoothGattCharacteristic characteristic = service.getCharacteristic( 468 charUuid.getUuid(), charInstId); 469 if (characteristic == null) return; 470 471 BluetoothGattDescriptor descriptor = characteristic.getDescriptor( 472 descrUuid.getUuid(), descrInstId); 473 if (descriptor == null) return; 474 475 if (status == 0) descriptor.setValue(value); 476 477 if ((status == GATT_INSUFFICIENT_AUTHENTICATION 478 || status == GATT_INSUFFICIENT_ENCRYPTION) 479 && mAuthRetry == false) { 480 try { 481 mAuthRetry = true; 482 mService.readDescriptor(mClientIf, address, 483 srvcType, srvcInstId, srvcUuid, charInstId, charUuid, 484 descrInstId, descrUuid, AUTHENTICATION_MITM); 485 return; 486 } catch (RemoteException e) { 487 Log.e(TAG,"",e); 488 } 489 } 490 491 mAuthRetry = true; 492 493 try { 494 mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status); 495 } catch (Exception ex) { 496 Log.w(TAG, "Unhandled exception in callback", ex); 497 } 498 } 499 500 /** 501 * Descriptor write operation complete. 502 * @hide 503 */ 504 public void onDescriptorWrite(String address, int status, int srvcType, 505 int srvcInstId, ParcelUuid srvcUuid, 506 int charInstId, ParcelUuid charUuid, 507 int descrInstId, ParcelUuid descrUuid) { 508 if (VDBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " UUID=" + charUuid); 509 510 if (!address.equals(mDevice.getAddress())) { 511 return; 512 } 513 514 synchronized(mDeviceBusy) { 515 mDeviceBusy = false; 516 } 517 518 BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(), 519 srvcInstId, srvcType); 520 if (service == null) return; 521 522 BluetoothGattCharacteristic characteristic = service.getCharacteristic( 523 charUuid.getUuid(), charInstId); 524 if (characteristic == null) return; 525 526 BluetoothGattDescriptor descriptor = characteristic.getDescriptor( 527 descrUuid.getUuid(), descrInstId); 528 if (descriptor == null) return; 529 530 if ((status == GATT_INSUFFICIENT_AUTHENTICATION 531 || status == GATT_INSUFFICIENT_ENCRYPTION) 532 && mAuthRetry == false) { 533 try { 534 mAuthRetry = true; 535 mService.writeDescriptor(mClientIf, address, 536 srvcType, srvcInstId, srvcUuid, charInstId, charUuid, 537 descrInstId, descrUuid, characteristic.getWriteType(), 538 AUTHENTICATION_MITM, descriptor.getValue()); 539 return; 540 } catch (RemoteException e) { 541 Log.e(TAG,"",e); 542 } 543 } 544 545 mAuthRetry = false; 546 547 try { 548 mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status); 549 } catch (Exception ex) { 550 Log.w(TAG, "Unhandled exception in callback", ex); 551 } 552 } 553 554 /** 555 * Prepared write transaction completed (or aborted) 556 * @hide 557 */ 558 public void onExecuteWrite(String address, int status) { 559 if (VDBG) Log.d(TAG, "onExecuteWrite() - Device=" + address 560 + " status=" + status); 561 if (!address.equals(mDevice.getAddress())) { 562 return; 563 } 564 565 synchronized(mDeviceBusy) { 566 mDeviceBusy = false; 567 } 568 569 try { 570 mCallback.onReliableWriteCompleted(BluetoothGatt.this, status); 571 } catch (Exception ex) { 572 Log.w(TAG, "Unhandled exception in callback", ex); 573 } 574 } 575 576 /** 577 * Remote device RSSI has been read 578 * @hide 579 */ 580 public void onReadRemoteRssi(String address, int rssi, int status) { 581 if (VDBG) Log.d(TAG, "onReadRemoteRssi() - Device=" + address + 582 " rssi=" + rssi + " status=" + status); 583 if (!address.equals(mDevice.getAddress())) { 584 return; 585 } 586 try { 587 mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status); 588 } catch (Exception ex) { 589 Log.w(TAG, "Unhandled exception in callback", ex); 590 } 591 } 592 593 /** 594 * Callback invoked when the MTU for a given connection changes 595 * @hide 596 */ 597 public void onConfigureMTU(String address, int mtu, int status) { 598 if (DBG) Log.d(TAG, "onConfigureMTU() - Device=" + address + 599 " mtu=" + mtu + " status=" + status); 600 if (!address.equals(mDevice.getAddress())) { 601 return; 602 } 603 try { 604 mCallback.onMtuChanged(BluetoothGatt.this, mtu, status); 605 } catch (Exception ex) { 606 Log.w(TAG, "Unhandled exception in callback", ex); 607 } 608 } 609 }; 610 611 /*package*/ BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device, 612 int transport) { 613 mContext = context; 614 mService = iGatt; 615 mDevice = device; 616 mTransport = transport; 617 mServices = new ArrayList<BluetoothGattService>(); 618 619 mConnState = CONN_STATE_IDLE; 620 } 621 622 /** 623 * Close this Bluetooth GATT client. 624 * 625 * Application should call this method as early as possible after it is done with 626 * this GATT client. 627 */ 628 public void close() { 629 if (DBG) Log.d(TAG, "close()"); 630 631 unregisterApp(); 632 mConnState = CONN_STATE_CLOSED; 633 } 634 635 /** 636 * Returns a service by UUID, instance and type. 637 * @hide 638 */ 639 /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid, 640 int instanceId, int type) { 641 for(BluetoothGattService svc : mServices) { 642 if (svc.getDevice().equals(device) && 643 svc.getType() == type && 644 svc.getInstanceId() == instanceId && 645 svc.getUuid().equals(uuid)) { 646 return svc; 647 } 648 } 649 return null; 650 } 651 652 653 /** 654 * Register an application callback to start using GATT. 655 * 656 * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered} 657 * is used to notify success or failure if the function returns true. 658 * 659 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 660 * 661 * @param callback GATT callback handler that will receive asynchronous callbacks. 662 * @return If true, the callback will be called to notify success or failure, 663 * false on immediate error 664 */ 665 private boolean registerApp(BluetoothGattCallback callback) { 666 if (DBG) Log.d(TAG, "registerApp()"); 667 if (mService == null) return false; 668 669 mCallback = callback; 670 UUID uuid = UUID.randomUUID(); 671 if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid); 672 673 try { 674 mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback); 675 } catch (RemoteException e) { 676 Log.e(TAG,"",e); 677 return false; 678 } 679 680 return true; 681 } 682 683 /** 684 * Unregister the current application and callbacks. 685 */ 686 private void unregisterApp() { 687 if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf); 688 if (mService == null || mClientIf == 0) return; 689 690 try { 691 mCallback = null; 692 mService.unregisterClient(mClientIf); 693 mClientIf = 0; 694 } catch (RemoteException e) { 695 Log.e(TAG,"",e); 696 } 697 } 698 699 /** 700 * Initiate a connection to a Bluetooth GATT capable device. 701 * 702 * <p>The connection may not be established right away, but will be 703 * completed when the remote device is available. A 704 * {@link BluetoothGattCallback#onConnectionStateChange} callback will be 705 * invoked when the connection state changes as a result of this function. 706 * 707 * <p>The autoConnect parameter determines whether to actively connect to 708 * the remote device, or rather passively scan and finalize the connection 709 * when the remote device is in range/available. Generally, the first ever 710 * connection to a device should be direct (autoConnect set to false) and 711 * subsequent connections to known devices should be invoked with the 712 * autoConnect parameter set to true. 713 * 714 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 715 * 716 * @param device Remote device to connect to 717 * @param autoConnect Whether to directly connect to the remote device (false) 718 * or to automatically connect as soon as the remote 719 * device becomes available (true). 720 * @return true, if the connection attempt was initiated successfully 721 */ 722 /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback) { 723 if (DBG) Log.d(TAG, "connect() - device: " + mDevice.getAddress() + ", auto: " + autoConnect); 724 synchronized(mStateLock) { 725 if (mConnState != CONN_STATE_IDLE) { 726 throw new IllegalStateException("Not idle"); 727 } 728 mConnState = CONN_STATE_CONNECTING; 729 } 730 if (!registerApp(callback)) { 731 synchronized(mStateLock) { 732 mConnState = CONN_STATE_IDLE; 733 } 734 Log.e(TAG, "Failed to register callback"); 735 return false; 736 } 737 738 // the connection will continue after successful callback registration 739 mAutoConnect = autoConnect; 740 return true; 741 } 742 743 /** 744 * Disconnects an established connection, or cancels a connection attempt 745 * currently in progress. 746 * 747 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 748 */ 749 public void disconnect() { 750 if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice.getAddress()); 751 if (mService == null || mClientIf == 0) return; 752 753 try { 754 mService.clientDisconnect(mClientIf, mDevice.getAddress()); 755 } catch (RemoteException e) { 756 Log.e(TAG,"",e); 757 } 758 } 759 760 /** 761 * Connect back to remote device. 762 * 763 * <p>This method is used to re-connect to a remote device after the 764 * connection has been dropped. If the device is not in range, the 765 * re-connection will be triggered once the device is back in range. 766 * 767 * @return true, if the connection attempt was initiated successfully 768 */ 769 public boolean connect() { 770 try { 771 mService.clientConnect(mClientIf, mDevice.getAddress(), 772 false, mTransport); // autoConnect is inverse of "isDirect" 773 return true; 774 } catch (RemoteException e) { 775 Log.e(TAG,"",e); 776 return false; 777 } 778 } 779 780 /** 781 * Return the remote bluetooth device this GATT client targets to 782 * 783 * @return remote bluetooth device 784 */ 785 public BluetoothDevice getDevice() { 786 return mDevice; 787 } 788 789 /** 790 * Discovers services offered by a remote device as well as their 791 * characteristics and descriptors. 792 * 793 * <p>This is an asynchronous operation. Once service discovery is completed, 794 * the {@link BluetoothGattCallback#onServicesDiscovered} callback is 795 * triggered. If the discovery was successful, the remote services can be 796 * retrieved using the {@link #getServices} function. 797 * 798 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 799 * 800 * @return true, if the remote service discovery has been started 801 */ 802 public boolean discoverServices() { 803 if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice.getAddress()); 804 if (mService == null || mClientIf == 0) return false; 805 806 mServices.clear(); 807 808 try { 809 mService.discoverServices(mClientIf, mDevice.getAddress()); 810 } catch (RemoteException e) { 811 Log.e(TAG,"",e); 812 return false; 813 } 814 815 return true; 816 } 817 818 /** 819 * Returns a list of GATT services offered by the remote device. 820 * 821 * <p>This function requires that service discovery has been completed 822 * for the given device. 823 * 824 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 825 * 826 * @return List of services on the remote device. Returns an empty list 827 * if service discovery has not yet been performed. 828 */ 829 public List<BluetoothGattService> getServices() { 830 List<BluetoothGattService> result = 831 new ArrayList<BluetoothGattService>(); 832 833 for (BluetoothGattService service : mServices) { 834 if (service.getDevice().equals(mDevice)) { 835 result.add(service); 836 } 837 } 838 839 return result; 840 } 841 842 /** 843 * Returns a {@link BluetoothGattService}, if the requested UUID is 844 * supported by the remote device. 845 * 846 * <p>This function requires that service discovery has been completed 847 * for the given device. 848 * 849 * <p>If multiple instances of the same service (as identified by UUID) 850 * exist, the first instance of the service is returned. 851 * 852 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 853 * 854 * @param uuid UUID of the requested service 855 * @return BluetoothGattService if supported, or null if the requested 856 * service is not offered by the remote device. 857 */ 858 public BluetoothGattService getService(UUID uuid) { 859 for (BluetoothGattService service : mServices) { 860 if (service.getDevice().equals(mDevice) && 861 service.getUuid().equals(uuid)) { 862 return service; 863 } 864 } 865 866 return null; 867 } 868 869 /** 870 * Reads the requested characteristic from the associated remote device. 871 * 872 * <p>This is an asynchronous operation. The result of the read operation 873 * is reported by the {@link BluetoothGattCallback#onCharacteristicRead} 874 * callback. 875 * 876 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 877 * 878 * @param characteristic Characteristic to read from the remote device 879 * @return true, if the read operation was initiated successfully 880 */ 881 public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) { 882 if ((characteristic.getProperties() & 883 BluetoothGattCharacteristic.PROPERTY_READ) == 0) return false; 884 885 if (VDBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid()); 886 if (mService == null || mClientIf == 0) return false; 887 888 BluetoothGattService service = characteristic.getService(); 889 if (service == null) return false; 890 891 BluetoothDevice device = service.getDevice(); 892 if (device == null) return false; 893 894 synchronized(mDeviceBusy) { 895 if (mDeviceBusy) return false; 896 mDeviceBusy = true; 897 } 898 899 try { 900 mService.readCharacteristic(mClientIf, device.getAddress(), 901 service.getType(), service.getInstanceId(), 902 new ParcelUuid(service.getUuid()), characteristic.getInstanceId(), 903 new ParcelUuid(characteristic.getUuid()), AUTHENTICATION_NONE); 904 } catch (RemoteException e) { 905 Log.e(TAG,"",e); 906 mDeviceBusy = false; 907 return false; 908 } 909 910 return true; 911 } 912 913 /** 914 * Writes a given characteristic and its values to the associated remote device. 915 * 916 * <p>Once the write operation has been completed, the 917 * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked, 918 * reporting the result of the operation. 919 * 920 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 921 * 922 * @param characteristic Characteristic to write on the remote device 923 * @return true, if the write operation was initiated successfully 924 */ 925 public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) { 926 if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0 927 && (characteristic.getProperties() & 928 BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) return false; 929 930 if (VDBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid()); 931 if (mService == null || mClientIf == 0) return false; 932 933 BluetoothGattService service = characteristic.getService(); 934 if (service == null) return false; 935 936 BluetoothDevice device = service.getDevice(); 937 if (device == null) return false; 938 939 synchronized(mDeviceBusy) { 940 if (mDeviceBusy) return false; 941 mDeviceBusy = true; 942 } 943 944 try { 945 mService.writeCharacteristic(mClientIf, device.getAddress(), 946 service.getType(), service.getInstanceId(), 947 new ParcelUuid(service.getUuid()), characteristic.getInstanceId(), 948 new ParcelUuid(characteristic.getUuid()), 949 characteristic.getWriteType(), AUTHENTICATION_NONE, 950 characteristic.getValue()); 951 } catch (RemoteException e) { 952 Log.e(TAG,"",e); 953 mDeviceBusy = false; 954 return false; 955 } 956 957 return true; 958 } 959 960 /** 961 * Reads the value for a given descriptor from the associated remote device. 962 * 963 * <p>Once the read operation has been completed, the 964 * {@link BluetoothGattCallback#onDescriptorRead} callback is 965 * triggered, signaling the result of the operation. 966 * 967 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 968 * 969 * @param descriptor Descriptor value to read from the remote device 970 * @return true, if the read operation was initiated successfully 971 */ 972 public boolean readDescriptor(BluetoothGattDescriptor descriptor) { 973 if (VDBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid()); 974 if (mService == null || mClientIf == 0) return false; 975 976 BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic(); 977 if (characteristic == null) return false; 978 979 BluetoothGattService service = characteristic.getService(); 980 if (service == null) return false; 981 982 BluetoothDevice device = service.getDevice(); 983 if (device == null) return false; 984 985 synchronized(mDeviceBusy) { 986 if (mDeviceBusy) return false; 987 mDeviceBusy = true; 988 } 989 990 try { 991 mService.readDescriptor(mClientIf, device.getAddress(), service.getType(), 992 service.getInstanceId(), new ParcelUuid(service.getUuid()), 993 characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()), 994 descriptor.getInstanceId(), new ParcelUuid(descriptor.getUuid()), 995 AUTHENTICATION_NONE); 996 } catch (RemoteException e) { 997 Log.e(TAG,"",e); 998 mDeviceBusy = false; 999 return false; 1000 } 1001 1002 return true; 1003 } 1004 1005 /** 1006 * Write the value of a given descriptor to the associated remote device. 1007 * 1008 * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is 1009 * triggered to report the result of the write operation. 1010 * 1011 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 1012 * 1013 * @param descriptor Descriptor to write to the associated remote device 1014 * @return true, if the write operation was initiated successfully 1015 */ 1016 public boolean writeDescriptor(BluetoothGattDescriptor descriptor) { 1017 if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid()); 1018 if (mService == null || mClientIf == 0) return false; 1019 1020 BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic(); 1021 if (characteristic == null) return false; 1022 1023 BluetoothGattService service = characteristic.getService(); 1024 if (service == null) return false; 1025 1026 BluetoothDevice device = service.getDevice(); 1027 if (device == null) return false; 1028 1029 synchronized(mDeviceBusy) { 1030 if (mDeviceBusy) return false; 1031 mDeviceBusy = true; 1032 } 1033 1034 try { 1035 mService.writeDescriptor(mClientIf, device.getAddress(), service.getType(), 1036 service.getInstanceId(), new ParcelUuid(service.getUuid()), 1037 characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()), 1038 descriptor.getInstanceId(), new ParcelUuid(descriptor.getUuid()), 1039 characteristic.getWriteType(), AUTHENTICATION_NONE, 1040 descriptor.getValue()); 1041 } catch (RemoteException e) { 1042 Log.e(TAG,"",e); 1043 mDeviceBusy = false; 1044 return false; 1045 } 1046 1047 return true; 1048 } 1049 1050 /** 1051 * Initiates a reliable write transaction for a given remote device. 1052 * 1053 * <p>Once a reliable write transaction has been initiated, all calls 1054 * to {@link #writeCharacteristic} are sent to the remote device for 1055 * verification and queued up for atomic execution. The application will 1056 * receive an {@link BluetoothGattCallback#onCharacteristicWrite} callback 1057 * in response to every {@link #writeCharacteristic} call and is responsible 1058 * for verifying if the value has been transmitted accurately. 1059 * 1060 * <p>After all characteristics have been queued up and verified, 1061 * {@link #executeReliableWrite} will execute all writes. If a characteristic 1062 * was not written correctly, calling {@link #abortReliableWrite} will 1063 * cancel the current transaction without commiting any values on the 1064 * remote device. 1065 * 1066 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 1067 * 1068 * @return true, if the reliable write transaction has been initiated 1069 */ 1070 public boolean beginReliableWrite() { 1071 if (VDBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice.getAddress()); 1072 if (mService == null || mClientIf == 0) return false; 1073 1074 try { 1075 mService.beginReliableWrite(mClientIf, mDevice.getAddress()); 1076 } catch (RemoteException e) { 1077 Log.e(TAG,"",e); 1078 return false; 1079 } 1080 1081 return true; 1082 } 1083 1084 /** 1085 * Executes a reliable write transaction for a given remote device. 1086 * 1087 * <p>This function will commit all queued up characteristic write 1088 * operations for a given remote device. 1089 * 1090 * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is 1091 * invoked to indicate whether the transaction has been executed correctly. 1092 * 1093 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 1094 * 1095 * @return true, if the request to execute the transaction has been sent 1096 */ 1097 public boolean executeReliableWrite() { 1098 if (VDBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice.getAddress()); 1099 if (mService == null || mClientIf == 0) return false; 1100 1101 synchronized(mDeviceBusy) { 1102 if (mDeviceBusy) return false; 1103 mDeviceBusy = true; 1104 } 1105 1106 try { 1107 mService.endReliableWrite(mClientIf, mDevice.getAddress(), true); 1108 } catch (RemoteException e) { 1109 Log.e(TAG,"",e); 1110 mDeviceBusy = false; 1111 return false; 1112 } 1113 1114 return true; 1115 } 1116 1117 /** 1118 * Cancels a reliable write transaction for a given device. 1119 * 1120 * <p>Calling this function will discard all queued characteristic write 1121 * operations for a given remote device. 1122 * 1123 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 1124 */ 1125 public void abortReliableWrite() { 1126 if (VDBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice.getAddress()); 1127 if (mService == null || mClientIf == 0) return; 1128 1129 try { 1130 mService.endReliableWrite(mClientIf, mDevice.getAddress(), false); 1131 } catch (RemoteException e) { 1132 Log.e(TAG,"",e); 1133 } 1134 } 1135 1136 /** 1137 * @deprecated Use {@link #abortReliableWrite()} 1138 */ 1139 public void abortReliableWrite(BluetoothDevice mDevice) { 1140 abortReliableWrite(); 1141 } 1142 1143 /** 1144 * Enable or disable notifications/indications for a given characteristic. 1145 * 1146 * <p>Once notifications are enabled for a characteristic, a 1147 * {@link BluetoothGattCallback#onCharacteristicChanged} callback will be 1148 * triggered if the remote device indicates that the given characteristic 1149 * has changed. 1150 * 1151 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 1152 * 1153 * @param characteristic The characteristic for which to enable notifications 1154 * @param enable Set to true to enable notifications/indications 1155 * @return true, if the requested notification status was set successfully 1156 */ 1157 public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic, 1158 boolean enable) { 1159 if (DBG) Log.d(TAG, "setCharacteristicNotification() - uuid: " + characteristic.getUuid() 1160 + " enable: " + enable); 1161 if (mService == null || mClientIf == 0) return false; 1162 1163 BluetoothGattService service = characteristic.getService(); 1164 if (service == null) return false; 1165 1166 BluetoothDevice device = service.getDevice(); 1167 if (device == null) return false; 1168 1169 try { 1170 mService.registerForNotification(mClientIf, device.getAddress(), 1171 service.getType(), service.getInstanceId(), 1172 new ParcelUuid(service.getUuid()), characteristic.getInstanceId(), 1173 new ParcelUuid(characteristic.getUuid()), 1174 enable); 1175 } catch (RemoteException e) { 1176 Log.e(TAG,"",e); 1177 return false; 1178 } 1179 1180 return true; 1181 } 1182 1183 /** 1184 * Clears the internal cache and forces a refresh of the services from the 1185 * remote device. 1186 * @hide 1187 */ 1188 public boolean refresh() { 1189 if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress()); 1190 if (mService == null || mClientIf == 0) return false; 1191 1192 try { 1193 mService.refreshDevice(mClientIf, mDevice.getAddress()); 1194 } catch (RemoteException e) { 1195 Log.e(TAG,"",e); 1196 return false; 1197 } 1198 1199 return true; 1200 } 1201 1202 /** 1203 * Read the RSSI for a connected remote device. 1204 * 1205 * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be 1206 * invoked when the RSSI value has been read. 1207 * 1208 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 1209 * 1210 * @return true, if the RSSI value has been requested successfully 1211 */ 1212 public boolean readRemoteRssi() { 1213 if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice.getAddress()); 1214 if (mService == null || mClientIf == 0) return false; 1215 1216 try { 1217 mService.readRemoteRssi(mClientIf, mDevice.getAddress()); 1218 } catch (RemoteException e) { 1219 Log.e(TAG,"",e); 1220 return false; 1221 } 1222 1223 return true; 1224 } 1225 1226 /** 1227 * Request an MTU size used for a given connection. 1228 * 1229 * <p>When performing a write request operation (write without response), 1230 * the data sent is truncated to the MTU size. This function may be used 1231 * to request a larger MTU size to be able to send more data at once. 1232 * 1233 * <p>A {@link BluetoothGattCallback#onMtuChanged} callback will indicate 1234 * whether this operation was successful. 1235 * 1236 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 1237 * 1238 * @return true, if the new MTU value has been requested successfully 1239 */ 1240 public boolean requestMtu(int mtu) { 1241 if (DBG) Log.d(TAG, "configureMTU() - device: " + mDevice.getAddress() 1242 + " mtu: " + mtu); 1243 if (mService == null || mClientIf == 0) return false; 1244 1245 try { 1246 mService.configureMTU(mClientIf, mDevice.getAddress(), mtu); 1247 } catch (RemoteException e) { 1248 Log.e(TAG,"",e); 1249 return false; 1250 } 1251 1252 return true; 1253 } 1254 1255 /** 1256 * Request a connection parameter update. 1257 * 1258 * <p>This function will send a connection parameter update request to the 1259 * remote device. 1260 * 1261 * @param connectionPriority Request a specific connection priority. Must be one of 1262 * {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED}, 1263 * {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH} 1264 * or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}. 1265 * @throws IllegalArgumentException If the parameters are outside of their 1266 * specified range. 1267 */ 1268 public boolean requestConnectionPriority(int connectionPriority) { 1269 if (connectionPriority < CONNECTION_PRIORITY_BALANCED || 1270 connectionPriority > CONNECTION_PRIORITY_LOW_POWER) { 1271 throw new IllegalArgumentException("connectionPriority not within valid range"); 1272 } 1273 1274 if (DBG) Log.d(TAG, "requestConnectionPriority() - params: " + connectionPriority); 1275 if (mService == null || mClientIf == 0) return false; 1276 1277 try { 1278 mService.connectionParameterUpdate(mClientIf, mDevice.getAddress(), connectionPriority); 1279 } catch (RemoteException e) { 1280 Log.e(TAG,"",e); 1281 return false; 1282 } 1283 1284 return true; 1285 } 1286 1287 /** 1288 * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} 1289 * with {@link BluetoothProfile#GATT} as argument 1290 * 1291 * @throws UnsupportedOperationException 1292 */ 1293 @Override 1294 public int getConnectionState(BluetoothDevice device) { 1295 throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead."); 1296 } 1297 1298 /** 1299 * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} 1300 * with {@link BluetoothProfile#GATT} as argument 1301 * 1302 * @throws UnsupportedOperationException 1303 */ 1304 @Override 1305 public List<BluetoothDevice> getConnectedDevices() { 1306 throw new UnsupportedOperationException 1307 ("Use BluetoothManager#getConnectedDevices instead."); 1308 } 1309 1310 /** 1311 * Not supported - please use 1312 * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])} 1313 * with {@link BluetoothProfile#GATT} as first argument 1314 * 1315 * @throws UnsupportedOperationException 1316 */ 1317 @Override 1318 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 1319 throw new UnsupportedOperationException 1320 ("Use BluetoothManager#getDevicesMatchingConnectionStates instead."); 1321 } 1322 } 1323