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