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