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