1 /* 2 * Copyright (C) 2019 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 com.android.car.connecteddevice.ble; 18 19 import static com.android.car.connecteddevice.util.SafeLog.logd; 20 import static com.android.car.connecteddevice.util.SafeLog.loge; 21 import static com.android.car.connecteddevice.util.SafeLog.logw; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.bluetooth.BluetoothAdapter; 26 import android.bluetooth.BluetoothDevice; 27 import android.bluetooth.BluetoothGatt; 28 import android.bluetooth.BluetoothGattCallback; 29 import android.bluetooth.BluetoothGattCharacteristic; 30 import android.bluetooth.BluetoothGattDescriptor; 31 import android.bluetooth.BluetoothGattServer; 32 import android.bluetooth.BluetoothGattServerCallback; 33 import android.bluetooth.BluetoothGattService; 34 import android.bluetooth.BluetoothManager; 35 import android.bluetooth.BluetoothProfile; 36 import android.bluetooth.le.AdvertiseCallback; 37 import android.bluetooth.le.AdvertiseData; 38 import android.bluetooth.le.AdvertiseSettings; 39 import android.bluetooth.le.BluetoothLeAdvertiser; 40 import android.content.Context; 41 import android.content.pm.PackageManager; 42 import android.os.Handler; 43 44 import com.android.car.connecteddevice.util.ByteUtils; 45 46 import java.util.HashSet; 47 import java.util.Set; 48 import java.util.UUID; 49 import java.util.concurrent.CopyOnWriteArraySet; 50 import java.util.concurrent.atomic.AtomicReference; 51 52 /** 53 * A generic class that manages BLE peripheral operations like start/stop advertising, notifying 54 * connects/disconnects and reading/writing values to GATT characteristics. 55 */ 56 // TODO(b/123248433) This could move to a separate comms library. 57 public class BlePeripheralManager { 58 private static final String TAG = "BlePeripheralManager"; 59 60 private static final int BLE_RETRY_LIMIT = 5; 61 private static final int BLE_RETRY_INTERVAL_MS = 1000; 62 63 private static final int GATT_SERVER_RETRY_LIMIT = 20; 64 private static final int GATT_SERVER_RETRY_DELAY_MS = 200; 65 66 // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth 67 // .service.generic_access.xml 68 private static final UUID GENERIC_ACCESS_PROFILE_UUID = 69 UUID.fromString("00001800-0000-1000-8000-00805f9b34fb"); 70 // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth 71 // .characteristic.gap.device_name.xml 72 private static final UUID DEVICE_NAME_UUID = 73 UUID.fromString("00002a00-0000-1000-8000-00805f9b34fb"); 74 75 private final Handler mHandler; 76 77 private final Context mContext; 78 private final Set<Callback> mCallbacks = new CopyOnWriteArraySet<>(); 79 private final Set<OnCharacteristicWriteListener> mWriteListeners = new HashSet<>(); 80 private final Set<OnCharacteristicReadListener> mReadListeners = new HashSet<>(); 81 private final AtomicReference<BluetoothGattServer> mGattServer = new AtomicReference<>(); 82 private final AtomicReference<BluetoothGatt> mBluetoothGatt = new AtomicReference<>(); 83 84 private int mMtuSize = 20; 85 86 private BluetoothManager mBluetoothManager; 87 private BluetoothLeAdvertiser mAdvertiser; 88 private int mAdvertiserStartCount; 89 private int mGattServerRetryStartCount; 90 private BluetoothGattService mBluetoothGattService; 91 private AdvertiseCallback mAdvertiseCallback; 92 private AdvertiseData mAdvertiseData; 93 BlePeripheralManager(Context context)94 public BlePeripheralManager(Context context) { 95 mContext = context; 96 mHandler = new Handler(mContext.getMainLooper()); 97 } 98 99 /** 100 * Registers the given callback to be notified of various events within the {@link 101 * BlePeripheralManager}. 102 * 103 * @param callback The callback to be notified. 104 */ registerCallback(@onNull Callback callback)105 void registerCallback(@NonNull Callback callback) { 106 mCallbacks.add(callback); 107 } 108 109 /** 110 * Unregisters a previously registered callback. 111 * 112 * @param callback The callback to unregister. 113 */ unregisterCallback(@onNull Callback callback)114 void unregisterCallback(@NonNull Callback callback) { 115 mCallbacks.remove(callback); 116 } 117 118 /** 119 * Adds a listener to be notified of a write to characteristics. 120 * 121 * @param listener The listener to invoke. 122 */ addOnCharacteristicWriteListener(@onNull OnCharacteristicWriteListener listener)123 void addOnCharacteristicWriteListener(@NonNull OnCharacteristicWriteListener listener) { 124 mWriteListeners.add(listener); 125 } 126 127 /** 128 * Removes the given listener from being notified of characteristic writes. 129 * 130 * @param listener The listener to remove. 131 */ removeOnCharacteristicWriteListener(@onNull OnCharacteristicWriteListener listener)132 void removeOnCharacteristicWriteListener(@NonNull OnCharacteristicWriteListener listener) { 133 mWriteListeners.remove(listener); 134 } 135 136 /** 137 * Adds a listener to be notified of reads to characteristics. 138 * 139 * @param listener The listener to invoke. 140 */ addOnCharacteristicReadListener(@onNull OnCharacteristicReadListener listener)141 void addOnCharacteristicReadListener(@NonNull OnCharacteristicReadListener listener) { 142 mReadListeners.add(listener); 143 } 144 145 /** 146 * Removes the given listener from being notified of characteristic reads. 147 * 148 * @param listener The listener to remove. 149 */ removeOnCharacteristicReadistener(@onNull OnCharacteristicReadListener listener)150 void removeOnCharacteristicReadistener(@NonNull OnCharacteristicReadListener listener) { 151 mReadListeners.remove(listener); 152 } 153 154 /** 155 * Returns the current MTU size. 156 * 157 * @return The size of the MTU in bytes. 158 */ getMtuSize()159 int getMtuSize() { 160 return mMtuSize; 161 } 162 163 /** 164 * Starts the GATT server with the given {@link BluetoothGattService} and begins advertising. 165 * 166 * <p>It is possible that BLE service is still in TURNING_ON state when this method is invoked. 167 * Therefore, several retries will be made to ensure advertising is started. 168 * 169 * @param service {@link BluetoothGattService} that will be discovered by clients 170 * @param data {@link AdvertiseData} data to advertise 171 * @param advertiseCallback {@link AdvertiseCallback} callback for advertiser 172 */ startAdvertising( BluetoothGattService service, AdvertiseData data, AdvertiseCallback advertiseCallback)173 void startAdvertising( 174 BluetoothGattService service, AdvertiseData data, AdvertiseCallback advertiseCallback) { 175 logd(TAG, "startAdvertising: " + service.getUuid()); 176 if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { 177 loge(TAG, "Attempted start advertising, but system does not support BLE. Ignoring."); 178 return; 179 } 180 // Clears previous session before starting advertising. 181 cleanup(); 182 mBluetoothGattService = service; 183 mAdvertiseCallback = advertiseCallback; 184 mAdvertiseData = data; 185 mGattServerRetryStartCount = 0; 186 mBluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE); 187 mGattServer.set(mBluetoothManager.openGattServer(mContext, mGattServerCallback)); 188 openGattServer(); 189 } 190 191 /** 192 * Stops the GATT server from advertising. 193 * 194 * @param advertiseCallback The callback that is associated with the advertisement. 195 */ stopAdvertising(AdvertiseCallback advertiseCallback)196 void stopAdvertising(AdvertiseCallback advertiseCallback) { 197 if (mAdvertiser != null) { 198 logd(TAG, "Stop advertising."); 199 mAdvertiser.stopAdvertising(advertiseCallback); 200 } 201 } 202 203 /** 204 * Notifies the characteristic change via {@link BluetoothGattServer} 205 */ notifyCharacteristicChanged( @onNull BluetoothDevice device, @NonNull BluetoothGattCharacteristic characteristic, boolean confirm)206 void notifyCharacteristicChanged( 207 @NonNull BluetoothDevice device, 208 @NonNull BluetoothGattCharacteristic characteristic, 209 boolean confirm) { 210 BluetoothGattServer gattServer = mGattServer.get(); 211 if (gattServer == null) { 212 return; 213 } 214 215 if (!gattServer.notifyCharacteristicChanged(device, characteristic, confirm)) { 216 loge(TAG, "notifyCharacteristicChanged failed"); 217 } 218 } 219 220 /** 221 * Connect the Gatt server of the remote device to retrieve device name. 222 */ retrieveDeviceName(BluetoothDevice device)223 final void retrieveDeviceName(BluetoothDevice device) { 224 mBluetoothGatt.compareAndSet(null, device.connectGatt(mContext, false, mGattCallback)); 225 } 226 227 /** 228 * Cleans up the BLE GATT server state. 229 */ cleanup()230 void cleanup() { 231 // Stops the advertiser, scanner and GATT server. This needs to be done to avoid leaks. 232 if (mAdvertiser != null) { 233 mAdvertiser.stopAdvertising(mAdvertiseCallback); 234 } 235 // Clears all registered listeners. IHU only supports single connection in peripheral role. 236 mReadListeners.clear(); 237 mWriteListeners.clear(); 238 mAdvertiser = null; 239 240 BluetoothGattServer gattServer = mGattServer.getAndSet(null); 241 if (gattServer == null) { 242 return; 243 } 244 245 logd(TAG, "stopGattServer"); 246 BluetoothGatt bluetoothGatt = mBluetoothGatt.getAndSet(null); 247 if (bluetoothGatt != null) { 248 gattServer.cancelConnection(bluetoothGatt.getDevice()); 249 bluetoothGatt.disconnect(); 250 } 251 gattServer.clearServices(); 252 gattServer.close(); 253 } 254 openGattServer()255 private void openGattServer() { 256 // Only open one Gatt server. 257 BluetoothGattServer gattServer = mGattServer.get(); 258 if (gattServer != null) { 259 logd(TAG, "Gatt Server created, retry count: " + mGattServerRetryStartCount); 260 gattServer.clearServices(); 261 gattServer.addService(mBluetoothGattService); 262 AdvertiseSettings settings = 263 new AdvertiseSettings.Builder() 264 .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) 265 .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) 266 .setConnectable(true) 267 .build(); 268 mAdvertiserStartCount = 0; 269 startAdvertisingInternally(settings, mAdvertiseData, mAdvertiseCallback); 270 mGattServerRetryStartCount = 0; 271 } else if (mGattServerRetryStartCount < GATT_SERVER_RETRY_LIMIT) { 272 mGattServer.set(mBluetoothManager.openGattServer(mContext, mGattServerCallback)); 273 mGattServerRetryStartCount++; 274 mHandler.postDelayed(() -> openGattServer(), GATT_SERVER_RETRY_DELAY_MS); 275 } else { 276 loge(TAG, "Gatt server not created - exceeded retry limit."); 277 } 278 } 279 startAdvertisingInternally( AdvertiseSettings settings, AdvertiseData data, AdvertiseCallback advertiseCallback)280 private void startAdvertisingInternally( 281 AdvertiseSettings settings, AdvertiseData data, AdvertiseCallback advertiseCallback) { 282 if (BluetoothAdapter.getDefaultAdapter() != null) { 283 mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser(); 284 } 285 286 if (mAdvertiser != null) { 287 logd(TAG, "Advertiser created, retry count: " + mAdvertiserStartCount); 288 mAdvertiser.startAdvertising(settings, data, advertiseCallback); 289 mAdvertiserStartCount = 0; 290 } else if (mAdvertiserStartCount < BLE_RETRY_LIMIT) { 291 mHandler.postDelayed( 292 () -> startAdvertisingInternally(settings, data, advertiseCallback), 293 BLE_RETRY_INTERVAL_MS); 294 mAdvertiserStartCount += 1; 295 } else { 296 loge(TAG, "Cannot start BLE Advertisement. Advertise Retry count: " 297 + mAdvertiserStartCount); 298 } 299 } 300 301 private final BluetoothGattServerCallback mGattServerCallback = 302 new BluetoothGattServerCallback() { 303 @Override 304 public void onConnectionStateChange(BluetoothDevice device, int status, 305 int newState) { 306 logd(TAG, "BLE Connection State Change: " + newState); 307 switch (newState) { 308 case BluetoothProfile.STATE_CONNECTED: 309 for (Callback callback : mCallbacks) { 310 callback.onRemoteDeviceConnected(device); 311 } 312 break; 313 case BluetoothProfile.STATE_DISCONNECTED: 314 for (Callback callback : mCallbacks) { 315 callback.onRemoteDeviceDisconnected(device); 316 } 317 break; 318 default: 319 logw(TAG, "Connection state not connecting or disconnecting; ignoring: " 320 + newState); 321 } 322 } 323 324 @Override 325 public void onServiceAdded(int status, BluetoothGattService service) { 326 logd(TAG, "Service added status: " + status + " uuid: " + service.getUuid()); 327 } 328 329 @Override 330 public void onCharacteristicWriteRequest( 331 BluetoothDevice device, 332 int requestId, 333 BluetoothGattCharacteristic characteristic, 334 boolean preparedWrite, 335 boolean responseNeeded, 336 int offset, 337 byte[] value) { 338 BluetoothGattServer gattServer = mGattServer.get(); 339 if (gattServer == null) { 340 return; 341 } 342 gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, 343 value); 344 for (OnCharacteristicWriteListener listener : mWriteListeners) { 345 listener.onCharacteristicWrite(device, characteristic, value); 346 } 347 } 348 349 @Override 350 public void onDescriptorWriteRequest( 351 BluetoothDevice device, 352 int requestId, 353 BluetoothGattDescriptor descriptor, 354 boolean preparedWrite, 355 boolean responseNeeded, 356 int offset, 357 byte[] value) { 358 logd(TAG, "Write request for descriptor: " 359 + descriptor.getUuid() 360 + "; value: " 361 + ByteUtils.byteArrayToHexString(value)); 362 BluetoothGattServer gattServer = mGattServer.get(); 363 if (gattServer == null) { 364 return; 365 } 366 gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, 367 value); 368 } 369 370 @Override 371 public void onMtuChanged(BluetoothDevice device, int mtu) { 372 logd(TAG, "onMtuChanged: " + mtu + " for device " + device.getAddress()); 373 374 mMtuSize = mtu; 375 376 for (Callback callback : mCallbacks) { 377 callback.onMtuSizeChanged(mtu); 378 } 379 } 380 381 @Override 382 public void onNotificationSent(BluetoothDevice device, int status) { 383 super.onNotificationSent(device, status); 384 if (status == BluetoothGatt.GATT_SUCCESS) { 385 logd(TAG, "Notification sent successfully. Device: " + device.getAddress() 386 + ", Status: " + status + ". Notifying all listeners."); 387 for (OnCharacteristicReadListener listener : mReadListeners) { 388 listener.onCharacteristicRead(device); 389 } 390 } else { 391 loge(TAG, "Notification failed. Device: " + device + ", Status: " 392 + status); 393 } 394 } 395 }; 396 397 private final BluetoothGattCallback mGattCallback = 398 new BluetoothGattCallback() { 399 @Override 400 public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 401 logd(TAG, "Gatt Connection State Change: " + newState); 402 switch (newState) { 403 case BluetoothProfile.STATE_CONNECTED: 404 logd(TAG, "Gatt connected"); 405 BluetoothGatt bluetoothGatt = mBluetoothGatt.get(); 406 if (bluetoothGatt == null) { 407 break; 408 } 409 bluetoothGatt.discoverServices(); 410 break; 411 case BluetoothProfile.STATE_DISCONNECTED: 412 logd(TAG, "Gatt Disconnected"); 413 break; 414 default: 415 logd(TAG, "Connection state not connecting or disconnecting; ignoring: " 416 + newState); 417 } 418 } 419 420 @Override 421 public void onServicesDiscovered(BluetoothGatt gatt, int status) { 422 logd(TAG, "Gatt Services Discovered"); 423 BluetoothGatt bluetoothGatt = mBluetoothGatt.get(); 424 if (bluetoothGatt == null) { 425 return; 426 } 427 BluetoothGattService gapService = bluetoothGatt.getService( 428 GENERIC_ACCESS_PROFILE_UUID); 429 if (gapService == null) { 430 loge(TAG, "Generic Access Service is null."); 431 return; 432 } 433 BluetoothGattCharacteristic deviceNameCharacteristic = 434 gapService.getCharacteristic(DEVICE_NAME_UUID); 435 if (deviceNameCharacteristic == null) { 436 loge(TAG, "Device Name Characteristic is null."); 437 return; 438 } 439 bluetoothGatt.readCharacteristic(deviceNameCharacteristic); 440 } 441 442 @Override 443 public void onCharacteristicRead( 444 BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, 445 int status) { 446 if (status == BluetoothGatt.GATT_SUCCESS) { 447 String deviceName = characteristic.getStringValue(0); 448 logd(TAG, "BLE Device Name: " + deviceName); 449 450 for (Callback callback : mCallbacks) { 451 callback.onDeviceNameRetrieved(deviceName); 452 } 453 } else { 454 loge(TAG, "Reading GAP Failed: " + status); 455 } 456 } 457 }; 458 459 /** 460 * Interface to be notified of various events within the {@link BlePeripheralManager}. 461 */ 462 interface Callback { 463 /** 464 * Triggered when the name of the remote device is retrieved. 465 * 466 * @param deviceName Name of the remote device. 467 */ onDeviceNameRetrieved(@ullable String deviceName)468 void onDeviceNameRetrieved(@Nullable String deviceName); 469 470 /** 471 * Triggered if a remote client has requested to change the MTU for a given connection. 472 * 473 * @param size The new MTU size. 474 */ onMtuSizeChanged(int size)475 void onMtuSizeChanged(int size); 476 477 /** 478 * Triggered when a device (GATT client) connected. 479 * 480 * @param device Remote device that connected on BLE. 481 */ onRemoteDeviceConnected(@onNull BluetoothDevice device)482 void onRemoteDeviceConnected(@NonNull BluetoothDevice device); 483 484 /** 485 * Triggered when a device (GATT client) disconnected. 486 * 487 * @param device Remote device that disconnected on BLE. 488 */ onRemoteDeviceDisconnected(@onNull BluetoothDevice device)489 void onRemoteDeviceDisconnected(@NonNull BluetoothDevice device); 490 } 491 492 /** 493 * An interface for classes that wish to be notified of writes to a characteristic. 494 */ 495 interface OnCharacteristicWriteListener { 496 /** 497 * Triggered when this BlePeripheralManager receives a write request from a remote device. 498 * 499 * @param device The bluetooth device that holds the characteristic. 500 * @param characteristic The characteristic that was written to. 501 * @param value The value that was written. 502 */ onCharacteristicWrite( @onNull BluetoothDevice device, @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value)503 void onCharacteristicWrite( 504 @NonNull BluetoothDevice device, 505 @NonNull BluetoothGattCharacteristic characteristic, 506 @NonNull byte[] value); 507 } 508 509 /** 510 * An interface for classes that wish to be notified of reads on a characteristic. 511 */ 512 interface OnCharacteristicReadListener { 513 /** 514 * Triggered when this BlePeripheralManager receives a read request from a remote device. 515 * 516 * @param device The bluetooth device that holds the characteristic. 517 */ onCharacteristicRead(@onNull BluetoothDevice device)518 void onCharacteristicRead(@NonNull BluetoothDevice device); 519 } 520 } 521