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.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothProfile; 22 import android.bluetooth.BluetoothProfile.ServiceListener; 23 import android.bluetooth.IBluetoothManager; 24 import android.bluetooth.IBluetoothStateChangeCallback; 25 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.ServiceConnection; 30 import android.os.IBinder; 31 import android.os.ParcelUuid; 32 import android.os.RemoteException; 33 import android.os.ServiceManager; 34 import android.util.Log; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.UUID; 39 40 /** 41 * Public API for the Bluetooth GATT Profile server role. 42 * 43 * <p>This class provides Bluetooth GATT server role functionality, 44 * allowing applications to create and advertise Bluetooth Smart services 45 * and characteristics. 46 * 47 * <p>BluetoothGattServer is a proxy object for controlling the Bluetooth Service 48 * via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the 49 * BluetoothGatt proxy object. 50 */ 51 public final class BluetoothGattServer implements BluetoothProfile { 52 private static final String TAG = "BluetoothGattServer"; 53 private static final boolean DBG = true; 54 55 private final Context mContext; 56 private BluetoothAdapter mAdapter; 57 private IBluetoothGatt mService; 58 private BluetoothGattServerCallback mCallback; 59 60 private Object mServerIfLock = new Object(); 61 private int mServerIf; 62 private List<BluetoothGattService> mServices; 63 64 private static final int CALLBACK_REG_TIMEOUT = 10000; 65 66 /** 67 * Bluetooth GATT interface callbacks 68 */ 69 private final IBluetoothGattServerCallback mBluetoothGattServerCallback = 70 new IBluetoothGattServerCallback.Stub() { 71 /** 72 * Application interface registered - app is ready to go 73 * @hide 74 */ 75 public void onServerRegistered(int status, int serverIf) { 76 if (DBG) Log.d(TAG, "onServerRegistered() - status=" + status 77 + " serverIf=" + serverIf); 78 synchronized(mServerIfLock) { 79 if (mCallback != null) { 80 mServerIf = serverIf; 81 mServerIfLock.notify(); 82 } else { 83 // registration timeout 84 Log.e(TAG, "onServerRegistered: mCallback is null"); 85 } 86 } 87 } 88 89 /** 90 * Callback reporting an LE scan result. 91 * @hide 92 */ 93 public void onScanResult(String address, int rssi, byte[] advData) { 94 if (DBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi); 95 // no op 96 } 97 98 /** 99 * Server connection state changed 100 * @hide 101 */ 102 public void onServerConnectionState(int status, int serverIf, 103 boolean connected, String address) { 104 if (DBG) Log.d(TAG, "onServerConnectionState() - status=" + status 105 + " serverIf=" + serverIf + " device=" + address); 106 try { 107 mCallback.onConnectionStateChange(mAdapter.getRemoteDevice(address), status, 108 connected ? BluetoothProfile.STATE_CONNECTED : 109 BluetoothProfile.STATE_DISCONNECTED); 110 } catch (Exception ex) { 111 Log.w(TAG, "Unhandled exception in callback", ex); 112 } 113 } 114 115 /** 116 * Service has been added 117 * @hide 118 */ 119 public void onServiceAdded(int status, int srvcType, 120 int srvcInstId, ParcelUuid srvcId) { 121 UUID srvcUuid = srvcId.getUuid(); 122 if (DBG) Log.d(TAG, "onServiceAdded() - service=" + srvcUuid 123 + "status=" + status); 124 125 BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType); 126 if (service == null) return; 127 128 try { 129 mCallback.onServiceAdded((int)status, service); 130 } catch (Exception ex) { 131 Log.w(TAG, "Unhandled exception in callback", ex); 132 } 133 } 134 135 /** 136 * Remote client characteristic read request. 137 * @hide 138 */ 139 public void onCharacteristicReadRequest(String address, int transId, 140 int offset, boolean isLong, int srvcType, int srvcInstId, 141 ParcelUuid srvcId, int charInstId, ParcelUuid charId) { 142 UUID srvcUuid = srvcId.getUuid(); 143 UUID charUuid = charId.getUuid(); 144 if (DBG) Log.d(TAG, "onCharacteristicReadRequest() - " 145 + "service=" + srvcUuid + ", characteristic=" + charUuid); 146 147 BluetoothDevice device = mAdapter.getRemoteDevice(address); 148 BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType); 149 if (service == null) return; 150 151 BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid); 152 if (characteristic == null) return; 153 154 try { 155 mCallback.onCharacteristicReadRequest(device, transId, offset, characteristic); 156 } catch (Exception ex) { 157 Log.w(TAG, "Unhandled exception in callback", ex); 158 } 159 } 160 161 /** 162 * Remote client descriptor read request. 163 * @hide 164 */ 165 public void onDescriptorReadRequest(String address, int transId, 166 int offset, boolean isLong, int srvcType, int srvcInstId, 167 ParcelUuid srvcId, int charInstId, ParcelUuid charId, 168 ParcelUuid descrId) { 169 UUID srvcUuid = srvcId.getUuid(); 170 UUID charUuid = charId.getUuid(); 171 UUID descrUuid = descrId.getUuid(); 172 if (DBG) Log.d(TAG, "onCharacteristicReadRequest() - " 173 + "service=" + srvcUuid + ", characteristic=" + charUuid 174 + "descriptor=" + descrUuid); 175 176 BluetoothDevice device = mAdapter.getRemoteDevice(address); 177 BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType); 178 if (service == null) return; 179 180 BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid); 181 if (characteristic == null) return; 182 183 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descrUuid); 184 if (descriptor == null) return; 185 186 try { 187 mCallback.onDescriptorReadRequest(device, transId, offset, descriptor); 188 } catch (Exception ex) { 189 Log.w(TAG, "Unhandled exception in callback", ex); 190 } 191 } 192 193 /** 194 * Remote client characteristic write request. 195 * @hide 196 */ 197 public void onCharacteristicWriteRequest(String address, int transId, 198 int offset, int length, boolean isPrep, boolean needRsp, 199 int srvcType, int srvcInstId, ParcelUuid srvcId, 200 int charInstId, ParcelUuid charId, byte[] value) { 201 UUID srvcUuid = srvcId.getUuid(); 202 UUID charUuid = charId.getUuid(); 203 if (DBG) Log.d(TAG, "onCharacteristicWriteRequest() - " 204 + "service=" + srvcUuid + ", characteristic=" + charUuid); 205 206 BluetoothDevice device = mAdapter.getRemoteDevice(address); 207 BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType); 208 if (service == null) return; 209 210 BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid); 211 if (characteristic == null) return; 212 213 try { 214 mCallback.onCharacteristicWriteRequest(device, transId, characteristic, 215 isPrep, needRsp, offset, value); 216 } catch (Exception ex) { 217 Log.w(TAG, "Unhandled exception in callback", ex); 218 } 219 220 } 221 222 /** 223 * Remote client descriptor write request. 224 * @hide 225 */ 226 public void onDescriptorWriteRequest(String address, int transId, 227 int offset, int length, boolean isPrep, boolean needRsp, 228 int srvcType, int srvcInstId, ParcelUuid srvcId, 229 int charInstId, ParcelUuid charId, ParcelUuid descrId, 230 byte[] value) { 231 UUID srvcUuid = srvcId.getUuid(); 232 UUID charUuid = charId.getUuid(); 233 UUID descrUuid = descrId.getUuid(); 234 if (DBG) Log.d(TAG, "onDescriptorWriteRequest() - " 235 + "service=" + srvcUuid + ", characteristic=" + charUuid 236 + "descriptor=" + descrUuid); 237 238 BluetoothDevice device = mAdapter.getRemoteDevice(address); 239 240 BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType); 241 if (service == null) return; 242 243 BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid); 244 if (characteristic == null) return; 245 246 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descrUuid); 247 if (descriptor == null) return; 248 249 try { 250 mCallback.onDescriptorWriteRequest(device, transId, descriptor, 251 isPrep, needRsp, offset, value); 252 } catch (Exception ex) { 253 Log.w(TAG, "Unhandled exception in callback", ex); 254 } 255 } 256 257 /** 258 * Execute pending writes. 259 * @hide 260 */ 261 public void onExecuteWrite(String address, int transId, 262 boolean execWrite) { 263 if (DBG) Log.d(TAG, "onExecuteWrite() - " 264 + "device=" + address + ", transId=" + transId 265 + "execWrite=" + execWrite); 266 267 BluetoothDevice device = mAdapter.getRemoteDevice(address); 268 if (device == null) return; 269 270 try { 271 mCallback.onExecuteWrite(device, transId, execWrite); 272 } catch (Exception ex) { 273 Log.w(TAG, "Unhandled exception in callback", ex); 274 } 275 } 276 }; 277 278 /** 279 * Create a BluetoothGattServer proxy object. 280 */ 281 /*package*/ BluetoothGattServer(Context context, IBluetoothGatt iGatt) { 282 mContext = context; 283 mService = iGatt; 284 mAdapter = BluetoothAdapter.getDefaultAdapter(); 285 mCallback = null; 286 mServerIf = 0; 287 mServices = new ArrayList<BluetoothGattService>(); 288 } 289 290 /** 291 * Close this GATT server instance. 292 * 293 * Application should call this method as early as possible after it is done with 294 * this GATT server. 295 */ 296 public void close() { 297 if (DBG) Log.d(TAG, "close()"); 298 unregisterCallback(); 299 } 300 301 /** 302 * Register an application callback to start using GattServer. 303 * 304 * <p>This is an asynchronous call. The callback is used to notify 305 * success or failure if the function returns true. 306 * 307 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 308 * 309 * @param callback GATT callback handler that will receive asynchronous 310 * callbacks. 311 * @return true, the callback will be called to notify success or failure, 312 * false on immediate error 313 */ 314 /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) { 315 if (DBG) Log.d(TAG, "registerCallback()"); 316 if (mService == null) { 317 Log.e(TAG, "GATT service not available"); 318 return false; 319 } 320 UUID uuid = UUID.randomUUID(); 321 if (DBG) Log.d(TAG, "registerCallback() - UUID=" + uuid); 322 323 synchronized(mServerIfLock) { 324 if (mCallback != null) { 325 Log.e(TAG, "App can register callback only once"); 326 return false; 327 } 328 329 mCallback = callback; 330 try { 331 mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback); 332 } catch (RemoteException e) { 333 Log.e(TAG,"",e); 334 mCallback = null; 335 return false; 336 } 337 338 try { 339 mServerIfLock.wait(CALLBACK_REG_TIMEOUT); 340 } catch (InterruptedException e) { 341 Log.e(TAG, "" + e); 342 mCallback = null; 343 } 344 345 if (mServerIf == 0) { 346 mCallback = null; 347 return false; 348 } else { 349 return true; 350 } 351 } 352 } 353 354 /** 355 * Unregister the current application and callbacks. 356 */ 357 private void unregisterCallback() { 358 if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf); 359 if (mService == null || mServerIf == 0) return; 360 361 try { 362 mCallback = null; 363 mService.unregisterServer(mServerIf); 364 mServerIf = 0; 365 } catch (RemoteException e) { 366 Log.e(TAG,"",e); 367 } 368 } 369 370 /** 371 * Returns a service by UUID, instance and type. 372 * @hide 373 */ 374 /*package*/ BluetoothGattService getService(UUID uuid, int instanceId, int type) { 375 for(BluetoothGattService svc : mServices) { 376 if (svc.getType() == type && 377 svc.getInstanceId() == instanceId && 378 svc.getUuid().equals(uuid)) { 379 return svc; 380 } 381 } 382 return null; 383 } 384 385 /** 386 * Initiate a connection to a Bluetooth GATT capable device. 387 * 388 * <p>The connection may not be established right away, but will be 389 * completed when the remote device is available. A 390 * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be 391 * invoked when the connection state changes as a result of this function. 392 * 393 * <p>The autoConnect paramter determines whether to actively connect to 394 * the remote device, or rather passively scan and finalize the connection 395 * when the remote device is in range/available. Generally, the first ever 396 * connection to a device should be direct (autoConnect set to false) and 397 * subsequent connections to known devices should be invoked with the 398 * autoConnect parameter set to true. 399 * 400 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 401 * 402 * @param autoConnect Whether to directly connect to the remote device (false) 403 * or to automatically connect as soon as the remote 404 * device becomes available (true). 405 * @return true, if the connection attempt was initiated successfully 406 */ 407 public boolean connect(BluetoothDevice device, boolean autoConnect) { 408 if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect); 409 if (mService == null || mServerIf == 0) return false; 410 411 try { 412 mService.serverConnect(mServerIf, device.getAddress(), 413 autoConnect ? false : true); // autoConnect is inverse of "isDirect" 414 } catch (RemoteException e) { 415 Log.e(TAG,"",e); 416 return false; 417 } 418 419 return true; 420 } 421 422 /** 423 * Disconnects an established connection, or cancels a connection attempt 424 * currently in progress. 425 * 426 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 427 * 428 * @param device Remote device 429 */ 430 public void cancelConnection(BluetoothDevice device) { 431 if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress()); 432 if (mService == null || mServerIf == 0) return; 433 434 try { 435 mService.serverDisconnect(mServerIf, device.getAddress()); 436 } catch (RemoteException e) { 437 Log.e(TAG,"",e); 438 } 439 } 440 441 /** 442 * Send a response to a read or write request to a remote device. 443 * 444 * <p>This function must be invoked in when a remote read/write request 445 * is received by one of these callback methods: 446 * 447 * <ul> 448 * <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest} 449 * <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest} 450 * <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest} 451 * <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest} 452 * </ul> 453 * 454 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 455 * 456 * @param device The remote device to send this response to 457 * @param requestId The ID of the request that was received with the callback 458 * @param status The status of the request to be sent to the remote devices 459 * @param offset Value offset for partial read/write response 460 * @param value The value of the attribute that was read/written (optional) 461 */ 462 public boolean sendResponse(BluetoothDevice device, int requestId, 463 int status, int offset, byte[] value) { 464 if (DBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress()); 465 if (mService == null || mServerIf == 0) return false; 466 467 try { 468 mService.sendResponse(mServerIf, device.getAddress(), requestId, 469 status, offset, value); 470 } catch (RemoteException e) { 471 Log.e(TAG,"",e); 472 return false; 473 } 474 return true; 475 } 476 477 /** 478 * Send a notification or indication that a local characteristic has been 479 * updated. 480 * 481 * <p>A notification or indication is sent to the remote device to signal 482 * that the characteristic has been updated. This function should be invoked 483 * for every client that requests notifications/indications by writing 484 * to the "Client Configuration" descriptor for the given characteristic. 485 * 486 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 487 * 488 * @param device The remote device to receive the notification/indication 489 * @param characteristic The local characteristic that has been updated 490 * @param confirm true to request confirmation from the client (indication), 491 * false to send a notification 492 * @return true, if the notification has been triggered successfully 493 */ 494 public boolean notifyCharacteristicChanged(BluetoothDevice device, 495 BluetoothGattCharacteristic characteristic, boolean confirm) { 496 if (DBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress()); 497 if (mService == null || mServerIf == 0) return false; 498 499 BluetoothGattService service = characteristic.getService(); 500 if (service == null) return false; 501 502 try { 503 mService.sendNotification(mServerIf, device.getAddress(), 504 service.getType(), service.getInstanceId(), 505 new ParcelUuid(service.getUuid()), characteristic.getInstanceId(), 506 new ParcelUuid(characteristic.getUuid()), confirm, 507 characteristic.getValue()); 508 } catch (RemoteException e) { 509 Log.e(TAG,"",e); 510 return false; 511 } 512 513 return true; 514 } 515 516 /** 517 * Add a service to the list of services to be hosted. 518 * 519 * <p>Once a service has been addded to the the list, the service and it's 520 * included characteristics will be provided by the local device. 521 * 522 * <p>If the local device has already exposed services when this function 523 * is called, a service update notification will be sent to all clients. 524 * 525 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 526 * 527 * @param service Service to be added to the list of services provided 528 * by this device. 529 * @return true, if the service has been added successfully 530 */ 531 public boolean addService(BluetoothGattService service) { 532 if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid()); 533 if (mService == null || mServerIf == 0) return false; 534 535 mServices.add(service); 536 537 try { 538 mService.beginServiceDeclaration(mServerIf, service.getType(), 539 service.getInstanceId(), service.getHandles(), 540 new ParcelUuid(service.getUuid()), service.isAdvertisePreferred()); 541 542 List<BluetoothGattService> includedServices = service.getIncludedServices(); 543 for (BluetoothGattService includedService : includedServices) { 544 mService.addIncludedService(mServerIf, 545 includedService.getType(), 546 includedService.getInstanceId(), 547 new ParcelUuid(includedService.getUuid())); 548 } 549 550 List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics(); 551 for (BluetoothGattCharacteristic characteristic : characteristics) { 552 int permission = ((characteristic.getKeySize() - 7) << 12) 553 + characteristic.getPermissions(); 554 mService.addCharacteristic(mServerIf, 555 new ParcelUuid(characteristic.getUuid()), 556 characteristic.getProperties(), permission); 557 558 List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors(); 559 for (BluetoothGattDescriptor descriptor: descriptors) { 560 permission = ((characteristic.getKeySize() - 7) << 12) 561 + descriptor.getPermissions(); 562 mService.addDescriptor(mServerIf, 563 new ParcelUuid(descriptor.getUuid()), permission); 564 } 565 } 566 567 mService.endServiceDeclaration(mServerIf); 568 } catch (RemoteException e) { 569 Log.e(TAG,"",e); 570 return false; 571 } 572 573 return true; 574 } 575 576 /** 577 * Removes a service from the list of services to be provided. 578 * 579 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 580 * 581 * @param service Service to be removed. 582 * @return true, if the service has been removed 583 */ 584 public boolean removeService(BluetoothGattService service) { 585 if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid()); 586 if (mService == null || mServerIf == 0) return false; 587 588 BluetoothGattService intService = getService(service.getUuid(), 589 service.getInstanceId(), service.getType()); 590 if (intService == null) return false; 591 592 try { 593 mService.removeService(mServerIf, service.getType(), 594 service.getInstanceId(), new ParcelUuid(service.getUuid())); 595 mServices.remove(intService); 596 } catch (RemoteException e) { 597 Log.e(TAG,"",e); 598 return false; 599 } 600 601 return true; 602 } 603 604 /** 605 * Remove all services from the list of provided services. 606 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 607 */ 608 public void clearServices() { 609 if (DBG) Log.d(TAG, "clearServices()"); 610 if (mService == null || mServerIf == 0) return; 611 612 try { 613 mService.clearServices(mServerIf); 614 mServices.clear(); 615 } catch (RemoteException e) { 616 Log.e(TAG,"",e); 617 } 618 } 619 620 /** 621 * Returns a list of GATT services offered by this device. 622 * 623 * <p>An application must call {@link #addService} to add a serice to the 624 * list of services offered by this device. 625 * 626 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 627 * 628 * @return List of services. Returns an empty list 629 * if no services have been added yet. 630 */ 631 public List<BluetoothGattService> getServices() { 632 return mServices; 633 } 634 635 /** 636 * Returns a {@link BluetoothGattService} from the list of services offered 637 * by this device. 638 * 639 * <p>If multiple instances of the same service (as identified by UUID) 640 * exist, the first instance of the service is returned. 641 * 642 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 643 * 644 * @param uuid UUID of the requested service 645 * @return BluetoothGattService if supported, or null if the requested 646 * service is not offered by this device. 647 */ 648 public BluetoothGattService getService(UUID uuid) { 649 for (BluetoothGattService service : mServices) { 650 if (service.getUuid().equals(uuid)) { 651 return service; 652 } 653 } 654 655 return null; 656 } 657 658 659 /** 660 * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} 661 * with {@link BluetoothProfile#GATT} as argument 662 * 663 * @throws UnsupportedOperationException 664 */ 665 @Override 666 public int getConnectionState(BluetoothDevice device) { 667 throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead."); 668 } 669 670 /** 671 * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} 672 * with {@link BluetoothProfile#GATT} as argument 673 * 674 * @throws UnsupportedOperationException 675 */ 676 @Override 677 public List<BluetoothDevice> getConnectedDevices() { 678 throw new UnsupportedOperationException 679 ("Use BluetoothManager#getConnectedDevices instead."); 680 } 681 682 /** 683 * Not supported - please use 684 * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])} 685 * with {@link BluetoothProfile#GATT} as first argument 686 * 687 * @throws UnsupportedOperationException 688 */ 689 @Override 690 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 691 throw new UnsupportedOperationException 692 ("Use BluetoothManager#getDevicesMatchingConnectionStates instead."); 693 } 694 } 695