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