BluetoothGattServer.java revision 0998ff13498ed004956d1de428eb0c4dcd33c1fb
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 17package android.bluetooth; 18 19import android.bluetooth.BluetoothAdapter; 20import android.bluetooth.BluetoothDevice; 21import android.bluetooth.BluetoothProfile; 22import android.bluetooth.BluetoothProfile.ServiceListener; 23import android.bluetooth.IBluetoothManager; 24import android.bluetooth.IBluetoothStateChangeCallback; 25 26import android.content.ComponentName; 27import android.content.Context; 28import android.content.Intent; 29import android.content.ServiceConnection; 30import android.os.IBinder; 31import android.os.ParcelUuid; 32import android.os.RemoteException; 33import android.os.ServiceManager; 34import android.util.Log; 35 36import java.util.ArrayList; 37import java.util.List; 38import 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 */ 51public 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 public void close() { 294 if (DBG) Log.d(TAG, "close()"); 295 unregisterCallback(); 296 } 297 298 /** 299 * Register an application callback to start using GattServer. 300 * 301 * <p>This is an asynchronous call. The callback is used to notify 302 * success or failure if the function returns true. 303 * 304 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 305 * 306 * @param callback GATT callback handler that will receive asynchronous 307 * callbacks. 308 * @return true, the callback will be called to notify success or failure, 309 * false on immediate error 310 */ 311 /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) { 312 if (DBG) Log.d(TAG, "registerCallback()"); 313 if (mService == null) { 314 Log.e(TAG, "GATT service not available"); 315 return false; 316 } 317 UUID uuid = UUID.randomUUID(); 318 if (DBG) Log.d(TAG, "registerCallback() - UUID=" + uuid); 319 320 synchronized(mServerIfLock) { 321 if (mCallback != null) { 322 Log.e(TAG, "App can register callback only once"); 323 return false; 324 } 325 326 mCallback = callback; 327 try { 328 mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback); 329 } catch (RemoteException e) { 330 Log.e(TAG,"",e); 331 mCallback = null; 332 return false; 333 } 334 335 try { 336 mServerIfLock.wait(CALLBACK_REG_TIMEOUT); 337 } catch (InterruptedException e) { 338 Log.e(TAG, "" + e); 339 mCallback = null; 340 } 341 342 if (mServerIf == 0) { 343 mCallback = null; 344 return false; 345 } else { 346 return true; 347 } 348 } 349 } 350 351 /** 352 * Unregister the current application and callbacks. 353 */ 354 private void unregisterCallback() { 355 if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf); 356 if (mService == null || mServerIf == 0) return; 357 358 try { 359 mCallback = null; 360 mService.unregisterServer(mServerIf); 361 mServerIf = 0; 362 } catch (RemoteException e) { 363 Log.e(TAG,"",e); 364 } 365 } 366 367 /** 368 * Returns a service by UUID, instance and type. 369 * @hide 370 */ 371 /*package*/ BluetoothGattService getService(UUID uuid, int instanceId, int type) { 372 for(BluetoothGattService svc : mServices) { 373 if (svc.getType() == type && 374 svc.getInstanceId() == instanceId && 375 svc.getUuid().equals(uuid)) { 376 return svc; 377 } 378 } 379 return null; 380 } 381 382 /** 383 * Initiate a connection to a Bluetooth GATT capable device. 384 * 385 * <p>The connection may not be established right away, but will be 386 * completed when the remote device is available. A 387 * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be 388 * invoked when the connection state changes as a result of this function. 389 * 390 * <p>The autoConnect paramter determines whether to actively connect to 391 * the remote device, or rather passively scan and finalize the connection 392 * when the remote device is in range/available. Generally, the first ever 393 * connection to a device should be direct (autoConnect set to false) and 394 * subsequent connections to known devices should be invoked with the 395 * autoConnect parameter set to true. 396 * 397 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 398 * 399 * @param autoConnect Whether to directly connect to the remote device (false) 400 * or to automatically connect as soon as the remote 401 * device becomes available (true). 402 * @return true, if the connection attempt was initiated successfully 403 */ 404 public boolean connect(BluetoothDevice device, boolean autoConnect) { 405 if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect); 406 if (mService == null || mServerIf == 0) return false; 407 408 try { 409 mService.serverConnect(mServerIf, device.getAddress(), 410 autoConnect ? false : true); // autoConnect is inverse of "isDirect" 411 } catch (RemoteException e) { 412 Log.e(TAG,"",e); 413 return false; 414 } 415 416 return true; 417 } 418 419 /** 420 * Disconnects an established connection, or cancels a connection attempt 421 * currently in progress. 422 * 423 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 424 * 425 * @param device Remote device 426 */ 427 public void cancelConnection(BluetoothDevice device) { 428 if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress()); 429 if (mService == null || mServerIf == 0) return; 430 431 try { 432 mService.serverDisconnect(mServerIf, device.getAddress()); 433 } catch (RemoteException e) { 434 Log.e(TAG,"",e); 435 } 436 } 437 438 /** 439 * Send a response to a read or write request to a remote device. 440 * 441 * <p>This function must be invoked in when a remote read/write request 442 * is received by one of these callback methods: 443 * 444 * <ul> 445 * <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest} 446 * <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest} 447 * <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest} 448 * <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest} 449 * </ul> 450 * 451 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 452 * 453 * @param device The remote device to send this response to 454 * @param requestId The ID of the request that was received with the callback 455 * @param status The status of the request to be sent to the remote devices 456 * @param offset Value offset for partial read/write response 457 * @param value The value of the attribute that was read/written (optional) 458 */ 459 public boolean sendResponse(BluetoothDevice device, int requestId, 460 int status, int offset, byte[] value) { 461 if (DBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress()); 462 if (mService == null || mServerIf == 0) return false; 463 464 try { 465 mService.sendResponse(mServerIf, device.getAddress(), requestId, 466 status, offset, value); 467 } catch (RemoteException e) { 468 Log.e(TAG,"",e); 469 return false; 470 } 471 return true; 472 } 473 474 /** 475 * Send a notification or indication that a local characteristic has been 476 * updated. 477 * 478 * <p>A notification or indication is sent to the remote device to signal 479 * that the characteristic has been updated. This function should be invoked 480 * for every client that requests notifications/indications by writing 481 * to the "Client Configuration" descriptor for the given characteristic. 482 * 483 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 484 * 485 * @param device The remote device to receive the notification/indication 486 * @param characteristic The local characteristic that has been updated 487 * @param confirm true to request confirmation from the client (indication), 488 * false to send a notification 489 * @return true, if the notification has been triggered successfully 490 */ 491 public boolean notifyCharacteristicChanged(BluetoothDevice device, 492 BluetoothGattCharacteristic characteristic, boolean confirm) { 493 if (DBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress()); 494 if (mService == null || mServerIf == 0) return false; 495 496 BluetoothGattService service = characteristic.getService(); 497 if (service == null) return false; 498 499 try { 500 mService.sendNotification(mServerIf, device.getAddress(), 501 service.getType(), service.getInstanceId(), 502 new ParcelUuid(service.getUuid()), characteristic.getInstanceId(), 503 new ParcelUuid(characteristic.getUuid()), confirm, 504 characteristic.getValue()); 505 } catch (RemoteException e) { 506 Log.e(TAG,"",e); 507 return false; 508 } 509 510 return true; 511 } 512 513 /** 514 * Add a service to the list of services to be hosted. 515 * 516 * <p>Once a service has been addded to the the list, the service and it's 517 * included characteristics will be provided by the local device. 518 * 519 * <p>If the local device has already exposed services when this function 520 * is called, a service update notification will be sent to all clients. 521 * 522 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 523 * 524 * @param service Service to be added to the list of services provided 525 * by this device. 526 * @return true, if the service has been added successfully 527 */ 528 public boolean addService(BluetoothGattService service) { 529 if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid()); 530 if (mService == null || mServerIf == 0) return false; 531 532 mServices.add(service); 533 534 try { 535 mService.beginServiceDeclaration(mServerIf, service.getType(), 536 service.getInstanceId(), service.getHandles(), 537 new ParcelUuid(service.getUuid())); 538 539 List<BluetoothGattService> includedServices = service.getIncludedServices(); 540 for (BluetoothGattService includedService : includedServices) { 541 mService.addIncludedService(mServerIf, 542 includedService.getType(), 543 includedService.getInstanceId(), 544 new ParcelUuid(includedService.getUuid())); 545 } 546 547 List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics(); 548 for (BluetoothGattCharacteristic characteristic : characteristics) { 549 int permission = ((characteristic.getKeySize() - 7) << 12) 550 + characteristic.getPermissions(); 551 mService.addCharacteristic(mServerIf, 552 new ParcelUuid(characteristic.getUuid()), 553 characteristic.getProperties(), permission); 554 555 List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors(); 556 for (BluetoothGattDescriptor descriptor: descriptors) { 557 permission = ((characteristic.getKeySize() - 7) << 12) 558 + descriptor.getPermissions(); 559 mService.addDescriptor(mServerIf, 560 new ParcelUuid(descriptor.getUuid()), permission); 561 } 562 } 563 564 mService.endServiceDeclaration(mServerIf); 565 } catch (RemoteException e) { 566 Log.e(TAG,"",e); 567 return false; 568 } 569 570 return true; 571 } 572 573 /** 574 * Removes a service from the list of services to be provided. 575 * 576 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 577 * 578 * @param service Service to be removed. 579 * @return true, if the service has been removed 580 */ 581 public boolean removeService(BluetoothGattService service) { 582 if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid()); 583 if (mService == null || mServerIf == 0) return false; 584 585 BluetoothGattService intService = getService(service.getUuid(), 586 service.getInstanceId(), service.getType()); 587 if (intService == null) return false; 588 589 try { 590 mService.removeService(mServerIf, service.getType(), 591 service.getInstanceId(), new ParcelUuid(service.getUuid())); 592 mServices.remove(intService); 593 } catch (RemoteException e) { 594 Log.e(TAG,"",e); 595 return false; 596 } 597 598 return true; 599 } 600 601 /** 602 * Remove all services from the list of provided services. 603 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 604 */ 605 public void clearServices() { 606 if (DBG) Log.d(TAG, "clearServices()"); 607 if (mService == null || mServerIf == 0) return; 608 609 try { 610 mService.clearServices(mServerIf); 611 mServices.clear(); 612 } catch (RemoteException e) { 613 Log.e(TAG,"",e); 614 } 615 } 616 617 /** 618 * Returns a list of GATT services offered by this device. 619 * 620 * <p>An application must call {@link #addService} to add a serice to the 621 * list of services offered by this device. 622 * 623 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 624 * 625 * @return List of services. Returns an empty list 626 * if no services have been added yet. 627 */ 628 public List<BluetoothGattService> getServices() { 629 return mServices; 630 } 631 632 /** 633 * Returns a {@link BluetoothGattService} from the list of services offered 634 * by this device. 635 * 636 * <p>If multiple instances of the same service (as identified by UUID) 637 * exist, the first instance of the service is returned. 638 * 639 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. 640 * 641 * @param uuid UUID of the requested service 642 * @return BluetoothGattService if supported, or null if the requested 643 * service is not offered by this device. 644 */ 645 public BluetoothGattService getService(UUID uuid) { 646 for (BluetoothGattService service : mServices) { 647 if (service.getUuid().equals(uuid)) { 648 return service; 649 } 650 } 651 652 return null; 653 } 654 655 656 /** 657 * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} 658 * with {@link BluetoothProfile#GATT} as argument 659 * 660 * @throws UnsupportedOperationException 661 */ 662 @Override 663 public int getConnectionState(BluetoothDevice device) { 664 throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead."); 665 } 666 667 /** 668 * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} 669 * with {@link BluetoothProfile#GATT} as argument 670 * 671 * @throws UnsupportedOperationException 672 */ 673 @Override 674 public List<BluetoothDevice> getConnectedDevices() { 675 throw new UnsupportedOperationException 676 ("Use BluetoothManager#getConnectedDevices instead."); 677 } 678 679 /** 680 * Not supported - please use 681 * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])} 682 * with {@link BluetoothProfile#GATT} as first argument 683 * 684 * @throws UnsupportedOperationException 685 */ 686 @Override 687 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 688 throw new UnsupportedOperationException 689 ("Use BluetoothManager#getDevicesMatchingConnectionStates instead."); 690 } 691} 692