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.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.RequiresNoPermission; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SuppressLint; 24 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 25 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; 26 import android.content.AttributionSource; 27 import android.os.IBinder; 28 import android.os.ParcelUuid; 29 import android.os.RemoteException; 30 import android.util.Log; 31 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 import java.util.ArrayList; 35 import java.util.List; 36 import java.util.UUID; 37 38 /** 39 * Public API for the Bluetooth GATT Profile server role. 40 * 41 * <p>This class provides Bluetooth GATT server role functionality, allowing applications to create 42 * Bluetooth Smart services and characteristics. 43 * 44 * <p>BluetoothGattServer is a proxy object for controlling the Bluetooth Service via IPC. Use 45 * {@link BluetoothManager#openGattServer} to get an instance of this class. 46 */ 47 public final class BluetoothGattServer implements BluetoothProfile { 48 private static final String TAG = "BluetoothGattServer"; 49 private static final boolean DBG = true; 50 private static final boolean VDBG = false; 51 52 private final IBluetoothGatt mService; 53 private final BluetoothAdapter mAdapter; 54 private final AttributionSource mAttributionSource; 55 56 private BluetoothGattServerCallback mCallback; 57 58 private final Object mServerIfLock = new Object(); 59 private int mServerIf; 60 private int mTransport; 61 private BluetoothGattService mPendingService; 62 private List<BluetoothGattService> mServices; 63 64 private static final int CALLBACK_REG_TIMEOUT = 10000; 65 // Max length of an attribute value, defined in gatt_api.h 66 private static final int GATT_MAX_ATTR_LEN = 512; 67 68 /** Bluetooth GATT interface callbacks */ 69 @SuppressLint("AndroidFrameworkBluetoothPermission") 70 private final IBluetoothGattServerCallback mBluetoothGattServerCallback = 71 new IBluetoothGattServerCallback.Stub() { 72 /** 73 * Application interface registered - app is ready to go 74 * 75 * @hide 76 */ 77 @Override 78 public void onServerRegistered(int status, int serverIf) { 79 if (DBG) { 80 Log.d( 81 TAG, 82 "onServerRegistered() - status=" 83 + status 84 + " serverIf=" 85 + serverIf); 86 } 87 synchronized (mServerIfLock) { 88 if (mCallback != null) { 89 mServerIf = serverIf; 90 mServerIfLock.notify(); 91 } else { 92 // registration timeout 93 Log.e(TAG, "onServerRegistered: mCallback is null"); 94 } 95 } 96 } 97 98 /** 99 * Server connection state changed 100 * 101 * @hide 102 */ 103 @Override 104 public void onServerConnectionState( 105 int status, int serverIf, boolean connected, String address) { 106 if (DBG) { 107 Log.d( 108 TAG, 109 "onServerConnectionState() - status=" 110 + status 111 + " serverIf=" 112 + serverIf 113 + " connected=" 114 + connected 115 + " device=" 116 + address); 117 } 118 try { 119 mCallback.onConnectionStateChange( 120 mAdapter.getRemoteDevice(address), 121 status, 122 connected 123 ? BluetoothProfile.STATE_CONNECTED 124 : BluetoothProfile.STATE_DISCONNECTED); 125 } catch (Exception ex) { 126 Log.w(TAG, "Unhandled exception in callback", ex); 127 } 128 } 129 130 /** 131 * Service has been added 132 * 133 * @hide 134 */ 135 @Override 136 public void onServiceAdded(int status, BluetoothGattService service) { 137 if (DBG) { 138 Log.d( 139 TAG, 140 "onServiceAdded() - handle=" 141 + service.getInstanceId() 142 + " uuid=" 143 + service.getUuid() 144 + " status=" 145 + status); 146 } 147 148 if (mPendingService == null) { 149 return; 150 } 151 152 BluetoothGattService tmp = mPendingService; 153 mPendingService = null; 154 155 // Rewrite newly assigned handles to existing service. 156 tmp.setInstanceId(service.getInstanceId()); 157 List<BluetoothGattCharacteristic> temp_chars = tmp.getCharacteristics(); 158 List<BluetoothGattCharacteristic> svc_chars = service.getCharacteristics(); 159 for (int i = 0; i < svc_chars.size(); i++) { 160 BluetoothGattCharacteristic temp_char = temp_chars.get(i); 161 BluetoothGattCharacteristic svc_char = svc_chars.get(i); 162 163 temp_char.setInstanceId(svc_char.getInstanceId()); 164 165 List<BluetoothGattDescriptor> temp_descs = temp_char.getDescriptors(); 166 List<BluetoothGattDescriptor> svc_descs = svc_char.getDescriptors(); 167 for (int j = 0; j < svc_descs.size(); j++) { 168 temp_descs.get(j).setInstanceId(svc_descs.get(j).getInstanceId()); 169 } 170 } 171 172 mServices.add(tmp); 173 174 try { 175 mCallback.onServiceAdded((int) status, tmp); 176 } catch (Exception ex) { 177 Log.w(TAG, "Unhandled exception in callback", ex); 178 } 179 } 180 181 /** 182 * Remote client characteristic read request. 183 * 184 * @hide 185 */ 186 @Override 187 public void onCharacteristicReadRequest( 188 String address, int transId, int offset, boolean isLong, int handle) { 189 if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle); 190 191 BluetoothDevice device = mAdapter.getRemoteDevice(address); 192 BluetoothGattCharacteristic characteristic = getCharacteristicByHandle(handle); 193 if (characteristic == null) { 194 Log.w(TAG, "onCharacteristicReadRequest() no char for handle " + handle); 195 return; 196 } 197 198 try { 199 mCallback.onCharacteristicReadRequest( 200 device, transId, offset, characteristic); 201 } catch (Exception ex) { 202 Log.w(TAG, "Unhandled exception in callback", ex); 203 } 204 } 205 206 /** 207 * Remote client descriptor read request. 208 * 209 * @hide 210 */ 211 @Override 212 public void onDescriptorReadRequest( 213 String address, int transId, int offset, boolean isLong, int handle) { 214 if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle); 215 216 BluetoothDevice device = mAdapter.getRemoteDevice(address); 217 BluetoothGattDescriptor descriptor = getDescriptorByHandle(handle); 218 if (descriptor == null) { 219 Log.w(TAG, "onDescriptorReadRequest() no desc for handle " + handle); 220 return; 221 } 222 223 try { 224 mCallback.onDescriptorReadRequest(device, transId, offset, descriptor); 225 } catch (Exception ex) { 226 Log.w(TAG, "Unhandled exception in callback", ex); 227 } 228 } 229 230 /** 231 * Remote client characteristic write request. 232 * 233 * @hide 234 */ 235 @Override 236 public void onCharacteristicWriteRequest( 237 String address, 238 int transId, 239 int offset, 240 int length, 241 boolean isPrep, 242 boolean needRsp, 243 int handle, 244 byte[] value) { 245 if (VDBG) Log.d(TAG, "onCharacteristicWriteRequest() - handle=" + handle); 246 247 BluetoothDevice device = mAdapter.getRemoteDevice(address); 248 BluetoothGattCharacteristic characteristic = getCharacteristicByHandle(handle); 249 if (characteristic == null) { 250 Log.w(TAG, "onCharacteristicWriteRequest() no char for handle " + handle); 251 return; 252 } 253 254 try { 255 mCallback.onCharacteristicWriteRequest( 256 device, transId, characteristic, isPrep, needRsp, offset, value); 257 } catch (Exception ex) { 258 Log.w(TAG, "Unhandled exception in callback", ex); 259 } 260 } 261 262 /** 263 * Remote client descriptor write request. 264 * 265 * @hide 266 */ 267 @Override 268 public void onDescriptorWriteRequest( 269 String address, 270 int transId, 271 int offset, 272 int length, 273 boolean isPrep, 274 boolean needRsp, 275 int handle, 276 byte[] value) { 277 if (VDBG) Log.d(TAG, "onDescriptorWriteRequest() - handle=" + handle); 278 279 BluetoothDevice device = mAdapter.getRemoteDevice(address); 280 BluetoothGattDescriptor descriptor = getDescriptorByHandle(handle); 281 if (descriptor == null) { 282 Log.w(TAG, "onDescriptorWriteRequest() no desc for handle " + handle); 283 return; 284 } 285 286 try { 287 mCallback.onDescriptorWriteRequest( 288 device, transId, descriptor, isPrep, needRsp, offset, value); 289 } catch (Exception ex) { 290 Log.w(TAG, "Unhandled exception in callback", ex); 291 } 292 } 293 294 /** 295 * Execute pending writes. 296 * 297 * @hide 298 */ 299 @Override 300 public void onExecuteWrite(String address, int transId, boolean execWrite) { 301 if (DBG) { 302 Log.d( 303 TAG, 304 "onExecuteWrite() - " 305 + "device=" 306 + address 307 + ", transId=" 308 + transId 309 + "execWrite=" 310 + execWrite); 311 } 312 313 BluetoothDevice device = mAdapter.getRemoteDevice(address); 314 if (device == null) return; 315 316 try { 317 mCallback.onExecuteWrite(device, transId, execWrite); 318 } catch (Exception ex) { 319 Log.w(TAG, "Unhandled exception in callback", ex); 320 } 321 } 322 323 /** 324 * A notification/indication has been sent. 325 * 326 * @hide 327 */ 328 @Override 329 public void onNotificationSent(String address, int status) { 330 if (VDBG) { 331 Log.d( 332 TAG, 333 "onNotificationSent() - " 334 + "device=" 335 + address 336 + ", status=" 337 + status); 338 } 339 340 BluetoothDevice device = mAdapter.getRemoteDevice(address); 341 if (device == null) return; 342 343 try { 344 mCallback.onNotificationSent(device, status); 345 } catch (Exception ex) { 346 Log.w(TAG, "Unhandled exception: " + ex); 347 } 348 } 349 350 /** 351 * The MTU for a connection has changed 352 * 353 * @hide 354 */ 355 @Override 356 public void onMtuChanged(String address, int mtu) { 357 if (DBG) { 358 Log.d(TAG, "onMtuChanged() - " + "device=" + address + ", mtu=" + mtu); 359 } 360 361 BluetoothDevice device = mAdapter.getRemoteDevice(address); 362 if (device == null) return; 363 364 try { 365 mCallback.onMtuChanged(device, mtu); 366 } catch (Exception ex) { 367 Log.w(TAG, "Unhandled exception: " + ex); 368 } 369 } 370 371 /** 372 * The PHY for a connection was updated 373 * 374 * @hide 375 */ 376 @Override 377 public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) { 378 if (DBG) { 379 Log.d( 380 TAG, 381 "onPhyUpdate() - " 382 + "device=" 383 + address 384 + ", txPHy=" 385 + txPhy 386 + ", rxPHy=" 387 + rxPhy); 388 } 389 390 BluetoothDevice device = mAdapter.getRemoteDevice(address); 391 if (device == null) return; 392 393 try { 394 mCallback.onPhyUpdate(device, txPhy, rxPhy, status); 395 } catch (Exception ex) { 396 Log.w(TAG, "Unhandled exception: " + ex); 397 } 398 } 399 400 /** 401 * The PHY for a connection was read 402 * 403 * @hide 404 */ 405 @Override 406 public void onPhyRead(String address, int txPhy, int rxPhy, int status) { 407 if (DBG) { 408 Log.d( 409 TAG, 410 "onPhyUpdate() - " 411 + "device=" 412 + address 413 + ", txPHy=" 414 + txPhy 415 + ", rxPHy=" 416 + rxPhy); 417 } 418 419 BluetoothDevice device = mAdapter.getRemoteDevice(address); 420 if (device == null) return; 421 422 try { 423 mCallback.onPhyRead(device, txPhy, rxPhy, status); 424 } catch (Exception ex) { 425 Log.w(TAG, "Unhandled exception: " + ex); 426 } 427 } 428 429 /** 430 * Callback invoked when the given connection is updated 431 * 432 * @hide 433 */ 434 @Override 435 public void onConnectionUpdated( 436 String address, int interval, int latency, int timeout, int status) { 437 if (DBG) { 438 Log.d( 439 TAG, 440 "onConnectionUpdated() - Device=" 441 + address 442 + " interval=" 443 + interval 444 + " latency=" 445 + latency 446 + " timeout=" 447 + timeout 448 + " status=" 449 + status); 450 } 451 BluetoothDevice device = mAdapter.getRemoteDevice(address); 452 if (device == null) return; 453 454 try { 455 mCallback.onConnectionUpdated(device, interval, latency, timeout, status); 456 } catch (Exception ex) { 457 Log.w(TAG, "Unhandled exception: " + ex); 458 } 459 } 460 461 /** 462 * Callback invoked when the given connection's subrate parameters are changed 463 * 464 * @hide 465 */ 466 @Override 467 public void onSubrateChange( 468 String address, 469 int subrateFactor, 470 int latency, 471 int contNum, 472 int timeout, 473 int status) { 474 if (DBG) { 475 Log.d( 476 TAG, 477 "onSubrateChange() - " 478 + "Device=" 479 + BluetoothUtils.toAnonymizedAddress(address) 480 + ", subrateFactor=" 481 + subrateFactor 482 + ", latency=" 483 + latency 484 + ", contNum=" 485 + contNum 486 + ", timeout=" 487 + timeout 488 + ", status=" 489 + status); 490 } 491 BluetoothDevice device = mAdapter.getRemoteDevice(address); 492 if (device == null) { 493 return; 494 } 495 496 try { 497 mCallback.onSubrateChange( 498 device, subrateFactor, latency, contNum, timeout, status); 499 } catch (Exception ex) { 500 Log.w(TAG, "Unhandled exception: " + ex); 501 } 502 } 503 }; 504 505 /** Create a BluetoothGattServer proxy object. */ BluetoothGattServer( IBluetoothGatt iGatt, int transport, BluetoothAdapter adapter)506 /* package */ BluetoothGattServer( 507 IBluetoothGatt iGatt, int transport, BluetoothAdapter adapter) { 508 mService = iGatt; 509 mAdapter = adapter; 510 mAttributionSource = adapter.getAttributionSource(); 511 mCallback = null; 512 mServerIf = 0; 513 mTransport = transport; 514 mServices = new ArrayList<BluetoothGattService>(); 515 } 516 517 /** 518 * Get the identifier of the BluetoothGattServer, or 0 if it is closed 519 * 520 * @hide 521 */ getServerIf()522 public int getServerIf() { 523 return mServerIf; 524 } 525 526 /** 527 * Returns a characteristic with given handle. 528 * 529 * @hide 530 */ getCharacteristicByHandle(int handle)531 /*package*/ BluetoothGattCharacteristic getCharacteristicByHandle(int handle) { 532 for (BluetoothGattService svc : mServices) { 533 for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) { 534 if (charac.getInstanceId() == handle) { 535 return charac; 536 } 537 } 538 } 539 return null; 540 } 541 542 /** 543 * Returns a descriptor with given handle. 544 * 545 * @hide 546 */ getDescriptorByHandle(int handle)547 /*package*/ BluetoothGattDescriptor getDescriptorByHandle(int handle) { 548 for (BluetoothGattService svc : mServices) { 549 for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) { 550 for (BluetoothGattDescriptor desc : charac.getDescriptors()) { 551 if (desc.getInstanceId() == handle) { 552 return desc; 553 } 554 } 555 } 556 } 557 return null; 558 } 559 560 /** @hide */ 561 @Override onServiceConnected(IBinder service)562 public void onServiceConnected(IBinder service) {} 563 564 /** @hide */ 565 @Override onServiceDisconnected()566 public void onServiceDisconnected() {} 567 568 /** @hide */ 569 @Override getAdapter()570 public BluetoothAdapter getAdapter() { 571 return mAdapter; 572 } 573 574 /** 575 * Close this GATT server instance. 576 * 577 * <p>Application should call this method as early as possible after it is done with this GATT 578 * server. 579 */ 580 @RequiresBluetoothConnectPermission 581 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) close()582 public void close() { 583 if (DBG) Log.d(TAG, "close()"); 584 unregisterCallback(); 585 } 586 587 /** 588 * Register an application callback to start using GattServer. 589 * 590 * <p>This is an asynchronous call. The callback is used to notify success or failure if the 591 * function returns true. 592 * 593 * @param callback GATT callback handler that will receive asynchronous callbacks. 594 * @return true, the callback will be called to notify success or failure, false on immediate 595 * error 596 */ 597 @RequiresLegacyBluetoothPermission 598 @RequiresBluetoothConnectPermission 599 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) registerCallback(BluetoothGattServerCallback callback)600 /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) { 601 return registerCallback(callback, false); 602 } 603 604 /** 605 * Register an application callback to start using GattServer. 606 * 607 * <p>This is an asynchronous call. The callback is used to notify success or failure if the 608 * function returns true. 609 * 610 * @param callback GATT callback handler that will receive asynchronous callbacks. 611 * @param eattSupport indicates if server can use eatt 612 * @return true, the callback will be called to notify success or failure, false on immediate 613 * error 614 * @hide 615 */ 616 @RequiresLegacyBluetoothPermission 617 @RequiresBluetoothConnectPermission 618 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 619 @SuppressWarnings("WaitNotInLoop") // TODO(b/314811467) registerCallback( BluetoothGattServerCallback callback, boolean eattSupport)620 /*package*/ boolean registerCallback( 621 BluetoothGattServerCallback callback, boolean eattSupport) { 622 if (DBG) Log.d(TAG, "registerCallback()"); 623 if (mService == null) { 624 Log.e(TAG, "GATT service not available"); 625 return false; 626 } 627 UUID uuid = UUID.randomUUID(); 628 if (DBG) Log.d(TAG, "registerCallback() - UUID=" + uuid); 629 630 synchronized (mServerIfLock) { 631 if (mCallback != null) { 632 Log.e(TAG, "App can register callback only once"); 633 return false; 634 } 635 636 mCallback = callback; 637 try { 638 mService.registerServer( 639 new ParcelUuid(uuid), 640 mBluetoothGattServerCallback, 641 eattSupport, 642 mAttributionSource); 643 } catch (RemoteException e) { 644 Log.e(TAG, "", e); 645 mCallback = null; 646 return false; 647 } 648 649 try { 650 mServerIfLock.wait(CALLBACK_REG_TIMEOUT); 651 } catch (InterruptedException e) { 652 Log.e(TAG, "" + e); 653 mCallback = null; 654 } 655 656 if (mServerIf == 0) { 657 mCallback = null; 658 return false; 659 } else { 660 return true; 661 } 662 } 663 } 664 665 /** Unregister the current application and callbacks. */ 666 @RequiresBluetoothConnectPermission 667 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) unregisterCallback()668 private void unregisterCallback() { 669 if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf); 670 if (mService == null || mServerIf == 0) return; 671 672 try { 673 mCallback = null; 674 mService.unregisterServer(mServerIf, mAttributionSource); 675 mServerIf = 0; 676 } catch (RemoteException e) { 677 Log.e(TAG, "", e); 678 } 679 } 680 681 /** 682 * Returns a service by UUID, instance and type. 683 * 684 * @hide 685 */ getService(UUID uuid, int instanceId, int type)686 /*package*/ BluetoothGattService getService(UUID uuid, int instanceId, int type) { 687 for (BluetoothGattService svc : mServices) { 688 if (svc.getType() == type 689 && svc.getInstanceId() == instanceId 690 && svc.getUuid().equals(uuid)) { 691 return svc; 692 } 693 } 694 return null; 695 } 696 697 /** 698 * Initiate a connection to a Bluetooth GATT capable device. 699 * 700 * <p>The connection may not be established right away, but will be completed when the remote 701 * device is available. A {@link BluetoothGattServerCallback#onConnectionStateChange} callback 702 * will be invoked when the connection state changes as a result of this function. 703 * 704 * <p>The autoConnect parameter determines whether to actively connect to the remote device, or 705 * rather passively scan and finalize the connection when the remote device is in 706 * range/available. Generally, the first ever connection to a device should be direct 707 * (autoConnect set to false) and subsequent connections to known devices should be invoked with 708 * the autoConnect parameter set to true. 709 * 710 * @param autoConnect Whether to directly connect to the remote device (false) or to 711 * automatically connect as soon as the remote device becomes available (true). 712 * @return true, if the connection attempt was initiated successfully 713 */ 714 @RequiresLegacyBluetoothPermission 715 @RequiresBluetoothConnectPermission 716 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) connect(BluetoothDevice device, boolean autoConnect)717 public boolean connect(BluetoothDevice device, boolean autoConnect) { 718 if (DBG) { 719 Log.d(TAG, "connect() - device: " + device + ", auto: " + autoConnect); 720 } 721 if (mService == null || mServerIf == 0) return false; 722 723 try { 724 // autoConnect is inverse of "isDirect" 725 mService.serverConnect( 726 mServerIf, 727 device.getAddress(), 728 device.getAddressType(), 729 !autoConnect, 730 mTransport, 731 mAttributionSource); 732 } catch (RemoteException e) { 733 Log.e(TAG, "", e); 734 return false; 735 } 736 737 return true; 738 } 739 740 /** 741 * Disconnects an established connection, or cancels a connection attempt currently in progress. 742 * 743 * @param device Remote device 744 */ 745 @RequiresLegacyBluetoothPermission 746 @RequiresBluetoothConnectPermission 747 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) cancelConnection(BluetoothDevice device)748 public void cancelConnection(BluetoothDevice device) { 749 if (DBG) Log.d(TAG, "cancelConnection() - device: " + device); 750 if (mService == null || mServerIf == 0) return; 751 752 try { 753 mService.serverDisconnect(mServerIf, device.getAddress(), mAttributionSource); 754 } catch (RemoteException e) { 755 Log.e(TAG, "", e); 756 } 757 } 758 759 /** 760 * Set the preferred connection PHY for this app. Please note that this is just a 761 * recommendation, whether the PHY change will happen depends on other applications preferences, 762 * local and remote controller capabilities. Controller can override these settings. 763 * 764 * <p>{@link BluetoothGattServerCallback#onPhyUpdate} will be triggered as a result of this 765 * call, even if no PHY change happens. It is also triggered when remote device updates the PHY. 766 * 767 * @param device The remote device to send this response to 768 * @param txPhy preferred transmitter PHY. Bitwise OR of any of {@link 769 * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link 770 * BluetoothDevice#PHY_LE_CODED_MASK}. 771 * @param rxPhy preferred receiver PHY. Bitwise OR of any of {@link 772 * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link 773 * BluetoothDevice#PHY_LE_CODED_MASK}. 774 * @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one 775 * of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} 776 * or {@link BluetoothDevice#PHY_OPTION_S8} 777 */ 778 @RequiresBluetoothConnectPermission 779 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) setPreferredPhy(BluetoothDevice device, int txPhy, int rxPhy, int phyOptions)780 public void setPreferredPhy(BluetoothDevice device, int txPhy, int rxPhy, int phyOptions) { 781 try { 782 mService.serverSetPreferredPhy( 783 mServerIf, device.getAddress(), txPhy, rxPhy, phyOptions, mAttributionSource); 784 } catch (RemoteException e) { 785 Log.e(TAG, "", e); 786 } 787 } 788 789 /** 790 * Read the current transmitter PHY and receiver PHY of the connection. The values are returned 791 * in {@link BluetoothGattServerCallback#onPhyRead} 792 * 793 * @param device The remote device to send this response to 794 */ 795 @RequiresBluetoothConnectPermission 796 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) readPhy(BluetoothDevice device)797 public void readPhy(BluetoothDevice device) { 798 try { 799 mService.serverReadPhy(mServerIf, device.getAddress(), mAttributionSource); 800 } catch (RemoteException e) { 801 Log.e(TAG, "", e); 802 } 803 } 804 805 /** 806 * Send a response to a read or write request to a remote device. 807 * 808 * <p>This function must be invoked in when a remote read/write request is received by one of 809 * these callback methods: 810 * 811 * <ul> 812 * <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest} 813 * <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest} 814 * <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest} 815 * <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest} 816 * </ul> 817 * 818 * @param device The remote device to send this response to 819 * @param requestId The ID of the request that was received with the callback 820 * @param status The status of the request to be sent to the remote devices 821 * @param offset Value offset for partial read/write response 822 * @param value The value of the attribute that was read/written (optional) 823 */ 824 @RequiresLegacyBluetoothPermission 825 @RequiresBluetoothConnectPermission 826 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) sendResponse( BluetoothDevice device, int requestId, int status, int offset, byte[] value)827 public boolean sendResponse( 828 BluetoothDevice device, int requestId, int status, int offset, byte[] value) { 829 if (VDBG) Log.d(TAG, "sendResponse() - device: " + device); 830 if (mService == null || mServerIf == 0) return false; 831 832 try { 833 mService.sendResponse( 834 mServerIf, 835 device.getAddress(), 836 requestId, 837 status, 838 offset, 839 value, 840 mAttributionSource); 841 } catch (RemoteException e) { 842 Log.e(TAG, "", e); 843 return false; 844 } 845 return true; 846 } 847 848 /** 849 * Send a notification or indication that a local characteristic has been updated. 850 * 851 * <p>A notification or indication is sent to the remote device to signal that the 852 * characteristic has been updated. This function should be invoked for every client that 853 * requests notifications/indications by writing to the "Client Configuration" descriptor for 854 * the given characteristic. 855 * 856 * @param device The remote device to receive the notification/indication 857 * @param characteristic The local characteristic that has been updated 858 * @param confirm true to request confirmation from the client (indication), false to send a 859 * notification 860 * @return true, if the notification has been triggered successfully 861 * @throws IllegalArgumentException if the characteristic value or service is null 862 * @deprecated Use {@link BluetoothGattServer#notifyCharacteristicChanged(BluetoothDevice, 863 * BluetoothGattCharacteristic, boolean, byte[])} as this is not memory safe. 864 */ 865 @Deprecated 866 @RequiresLegacyBluetoothPermission 867 @RequiresBluetoothConnectPermission 868 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) notifyCharacteristicChanged( BluetoothDevice device, BluetoothGattCharacteristic characteristic, boolean confirm)869 public boolean notifyCharacteristicChanged( 870 BluetoothDevice device, BluetoothGattCharacteristic characteristic, boolean confirm) { 871 return notifyCharacteristicChanged( 872 device, characteristic, confirm, characteristic.getValue()) 873 == BluetoothStatusCodes.SUCCESS; 874 } 875 876 /** @hide */ 877 @Retention(RetentionPolicy.SOURCE) 878 @IntDef( 879 value = { 880 BluetoothStatusCodes.SUCCESS, 881 BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION, 882 BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED, 883 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, 884 BluetoothStatusCodes.ERROR_UNKNOWN 885 }) 886 public @interface NotifyCharacteristicReturnValues {} 887 888 /** 889 * Send a notification or indication that a local characteristic has been updated. 890 * 891 * <p>A notification or indication is sent to the remote device to signal that the 892 * characteristic has been updated. This function should be invoked for every client that 893 * requests notifications/indications by writing to the "Client Configuration" descriptor for 894 * the given characteristic. 895 * 896 * @param device the remote device to receive the notification/indication 897 * @param characteristic the local characteristic that has been updated 898 * @param confirm {@code true} to request confirmation from the client (indication) or {@code 899 * false} to send a notification 900 * @param value the characteristic value 901 * @return whether the notification has been triggered successfully 902 * @throws IllegalArgumentException if the characteristic value or service is null 903 */ 904 @RequiresLegacyBluetoothPermission 905 @RequiresBluetoothConnectPermission 906 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 907 @NotifyCharacteristicReturnValues notifyCharacteristicChanged( @onNull BluetoothDevice device, @NonNull BluetoothGattCharacteristic characteristic, boolean confirm, @NonNull byte[] value)908 public int notifyCharacteristicChanged( 909 @NonNull BluetoothDevice device, 910 @NonNull BluetoothGattCharacteristic characteristic, 911 boolean confirm, 912 @NonNull byte[] value) { 913 if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device); 914 if (mService == null || mServerIf == 0) { 915 return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND; 916 } 917 918 if (characteristic == null) { 919 throw new IllegalArgumentException("characteristic must not be null"); 920 } 921 if (device == null) { 922 throw new IllegalArgumentException("device must not be null"); 923 } 924 if (value.length > GATT_MAX_ATTR_LEN) { 925 throw new IllegalArgumentException( 926 "notification should not be longer than max length of an attribute value"); 927 } 928 BluetoothGattService service = characteristic.getService(); 929 if (service == null) { 930 throw new IllegalArgumentException("Characteristic must have a non-null service"); 931 } 932 if (value == null) { 933 throw new IllegalArgumentException("Characteristic value must not be null"); 934 } 935 936 try { 937 return mService.sendNotification( 938 mServerIf, 939 device.getAddress(), 940 characteristic.getInstanceId(), 941 confirm, 942 value, 943 mAttributionSource); 944 } catch (RemoteException e) { 945 Log.e(TAG, "", e); 946 throw e.rethrowAsRuntimeException(); 947 } 948 } 949 950 /** 951 * Add a service to the list of services to be hosted. 952 * 953 * <p>Once a service has been added to the list, the service and its included characteristics 954 * will be provided by the local device. 955 * 956 * <p>If the local device has already exposed services when this function is called, a service 957 * update notification will be sent to all clients. 958 * 959 * <p>The {@link BluetoothGattServerCallback#onServiceAdded} callback will indicate whether this 960 * service has been added successfully. Do not add another service before this callback. 961 * 962 * @param service Service to be added to the list of services provided by this device. 963 * @return true, if the request to add service has been initiated 964 */ 965 @RequiresLegacyBluetoothPermission 966 @RequiresBluetoothConnectPermission 967 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) addService(BluetoothGattService service)968 public boolean addService(BluetoothGattService service) { 969 if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid()); 970 if (mService == null || mServerIf == 0) return false; 971 972 mPendingService = service; 973 974 try { 975 mService.addService(mServerIf, service, mAttributionSource); 976 } catch (RemoteException e) { 977 Log.e(TAG, "", e); 978 return false; 979 } 980 981 return true; 982 } 983 984 /** 985 * Removes a service from the list of services to be provided. 986 * 987 * @param service Service to be removed. 988 * @return true, if the service has been removed 989 */ 990 @RequiresLegacyBluetoothPermission 991 @RequiresBluetoothConnectPermission 992 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) removeService(BluetoothGattService service)993 public boolean removeService(BluetoothGattService service) { 994 if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid()); 995 if (mService == null || mServerIf == 0) return false; 996 997 BluetoothGattService intService = 998 getService(service.getUuid(), service.getInstanceId(), service.getType()); 999 if (intService == null) return false; 1000 1001 try { 1002 mService.removeService(mServerIf, service.getInstanceId(), mAttributionSource); 1003 mServices.remove(intService); 1004 } catch (RemoteException e) { 1005 Log.e(TAG, "", e); 1006 return false; 1007 } 1008 1009 return true; 1010 } 1011 1012 /** Remove all services from the list of provided services. */ 1013 @RequiresLegacyBluetoothPermission 1014 @RequiresBluetoothConnectPermission 1015 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) clearServices()1016 public void clearServices() { 1017 if (DBG) Log.d(TAG, "clearServices()"); 1018 if (mService == null || mServerIf == 0) return; 1019 1020 try { 1021 mService.clearServices(mServerIf, mAttributionSource); 1022 mServices.clear(); 1023 } catch (RemoteException e) { 1024 Log.e(TAG, "", e); 1025 } 1026 } 1027 1028 /** 1029 * Returns a list of GATT services offered by this device. 1030 * 1031 * <p>An application must call {@link #addService} to add a service to the list of services 1032 * offered by this device. 1033 * 1034 * @return List of services. Returns an empty list if no services have been added yet. 1035 */ 1036 @RequiresLegacyBluetoothPermission 1037 @RequiresNoPermission getServices()1038 public List<BluetoothGattService> getServices() { 1039 return mServices; 1040 } 1041 1042 /** 1043 * Returns a {@link BluetoothGattService} from the list of services offered by this device. 1044 * 1045 * <p>If multiple instances of the same service (as identified by UUID) exist, the first 1046 * instance of the service is returned. 1047 * 1048 * @param uuid UUID of the requested service 1049 * @return BluetoothGattService if supported, or null if the requested service is not offered by 1050 * this device. 1051 */ 1052 @RequiresLegacyBluetoothPermission 1053 @RequiresNoPermission getService(UUID uuid)1054 public BluetoothGattService getService(UUID uuid) { 1055 for (BluetoothGattService service : mServices) { 1056 if (service.getUuid().equals(uuid)) { 1057 return service; 1058 } 1059 } 1060 1061 return null; 1062 } 1063 1064 /** 1065 * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} with {@link 1066 * BluetoothProfile#GATT} as argument 1067 * 1068 * @throws UnsupportedOperationException on every call 1069 */ 1070 @Override 1071 @RequiresNoPermission getConnectionState(BluetoothDevice device)1072 public int getConnectionState(BluetoothDevice device) { 1073 throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead."); 1074 } 1075 1076 /** 1077 * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} with {@link 1078 * BluetoothProfile#GATT} as argument 1079 * 1080 * @throws UnsupportedOperationException on every call 1081 */ 1082 @Override 1083 @RequiresNoPermission getConnectedDevices()1084 public List<BluetoothDevice> getConnectedDevices() { 1085 throw new UnsupportedOperationException( 1086 "Use BluetoothManager#getConnectedDevices instead."); 1087 } 1088 1089 /** 1090 * Not supported - please use {@link BluetoothManager#getDevicesMatchingConnectionStates(int, 1091 * int[])} with {@link BluetoothProfile#GATT} as first argument 1092 * 1093 * @throws UnsupportedOperationException on every call 1094 */ 1095 @Override 1096 @RequiresNoPermission getDevicesMatchingConnectionStates(int[] states)1097 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 1098 throw new UnsupportedOperationException( 1099 "Use BluetoothManager#getDevicesMatchingConnectionStates instead."); 1100 } 1101 } 1102