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 package com.android.car.trust; 17 18 import static android.bluetooth.BluetoothProfile.GATT_SERVER; 19 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothGatt; 23 import android.bluetooth.BluetoothGattCallback; 24 import android.bluetooth.BluetoothGattCharacteristic; 25 import android.bluetooth.BluetoothGattDescriptor; 26 import android.bluetooth.BluetoothGattServer; 27 import android.bluetooth.BluetoothGattServerCallback; 28 import android.bluetooth.BluetoothGattService; 29 import android.bluetooth.BluetoothManager; 30 import android.bluetooth.BluetoothProfile; 31 import android.bluetooth.le.AdvertiseCallback; 32 import android.bluetooth.le.AdvertiseData; 33 import android.bluetooth.le.AdvertiseSettings; 34 import android.bluetooth.le.BluetoothLeAdvertiser; 35 import android.content.Context; 36 import android.content.pm.PackageManager; 37 import android.os.Handler; 38 import android.util.Log; 39 40 import androidx.annotation.Nullable; 41 42 import com.android.car.Utils; 43 44 import java.util.UUID; 45 46 /** 47 * A generic class that manages BLE operations like start/stop advertising, notifying connects/ 48 * disconnects and reading/writing values to GATT characteristics. 49 * 50 * TODO(b/123248433) This could move to a separate comms library. 51 */ 52 public abstract class BleManager { 53 private static final String TAG = BleManager.class.getSimpleName(); 54 55 private static final int BLE_RETRY_LIMIT = 5; 56 private static final int BLE_RETRY_INTERVAL_MS = 1000; 57 58 private static final int GATT_SERVER_RETRY_LIMIT = 20; 59 private static final int GATT_SERVER_RETRY_DELAY_MS = 200; 60 61 // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth 62 // .service.generic_access.xml 63 private static final UUID GENERIC_ACCESS_PROFILE_UUID = 64 UUID.fromString("00001800-0000-1000-8000-00805f9b34fb"); 65 //https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth 66 // .characteristic.gap.device_name.xml 67 private static final UUID DEVICE_NAME_UUID = 68 UUID.fromString("00002a00-0000-1000-8000-00805f9b34fb"); 69 70 private final Handler mHandler = new Handler(); 71 72 private final Context mContext; 73 private BluetoothManager mBluetoothManager; 74 private BluetoothLeAdvertiser mAdvertiser; 75 private BluetoothGattServer mGattServer; 76 private BluetoothGatt mBluetoothGatt; 77 private int mAdvertiserStartCount; 78 private int mGattServerRetryStartCount; 79 private BluetoothGattService mBluetoothGattService; 80 private AdvertiseCallback mAdvertiseCallback; 81 private AdvertiseData mData; 82 BleManager(Context context)83 BleManager(Context context) { 84 mContext = context; 85 } 86 87 /** 88 * Starts the GATT server with the given {@link BluetoothGattService} and begins 89 * advertising. 90 * 91 * <p>It is possible that BLE service is still in TURNING_ON state when this method is invoked. 92 * Therefore, several retries will be made to ensure advertising is started. 93 * 94 * @param service {@link BluetoothGattService} that will be discovered by clients 95 * @param data {@link AdvertiseData} data to advertise 96 * @param advertiseCallback {@link AdvertiseCallback} callback for advertiser 97 */ startAdvertising(BluetoothGattService service, AdvertiseData data, AdvertiseCallback advertiseCallback)98 protected void startAdvertising(BluetoothGattService service, AdvertiseData data, 99 AdvertiseCallback advertiseCallback) { 100 if (Log.isLoggable(TAG, Log.DEBUG)) { 101 Log.d(TAG, "startAdvertising: " + service.getUuid().toString()); 102 } 103 if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { 104 Log.e(TAG, "System does not support BLE"); 105 return; 106 } 107 108 mBluetoothGattService = service; 109 mAdvertiseCallback = advertiseCallback; 110 mData = data; 111 mGattServerRetryStartCount = 0; 112 mBluetoothManager = (BluetoothManager) mContext.getSystemService( 113 Context.BLUETOOTH_SERVICE); 114 openGattServer(); 115 } 116 openGattServer()117 private void openGattServer() { 118 // Only open one Gatt server. 119 if (mGattServer != null) { 120 if (Log.isLoggable(TAG, Log.DEBUG)) { 121 Log.d(TAG, "Gatt Server created, retry count: " + mGattServerRetryStartCount); 122 } 123 mGattServer.clearServices(); 124 mGattServer.addService(mBluetoothGattService); 125 AdvertiseSettings settings = new AdvertiseSettings.Builder() 126 .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) 127 .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) 128 .setConnectable(true) 129 .build(); 130 mAdvertiserStartCount = 0; 131 startAdvertisingInternally(settings, mData, mAdvertiseCallback); 132 mGattServerRetryStartCount = 0; 133 } else if (mGattServerRetryStartCount < GATT_SERVER_RETRY_LIMIT) { 134 mGattServer = mBluetoothManager.openGattServer(mContext, mGattServerCallback); 135 mGattServerRetryStartCount++; 136 mHandler.postDelayed(() -> openGattServer(), GATT_SERVER_RETRY_DELAY_MS); 137 } else { 138 Log.e(TAG, "Gatt server not created - exceeded retry limit."); 139 } 140 } 141 startAdvertisingInternally(AdvertiseSettings settings, AdvertiseData data, AdvertiseCallback advertiseCallback)142 private void startAdvertisingInternally(AdvertiseSettings settings, AdvertiseData data, 143 AdvertiseCallback advertiseCallback) { 144 if (BluetoothAdapter.getDefaultAdapter() != null) { 145 mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser(); 146 } 147 148 if (mAdvertiser != null) { 149 if (Log.isLoggable(TAG, Log.DEBUG)) { 150 Log.d(TAG, "Advertiser created, retry count: " + mAdvertiserStartCount); 151 } 152 mAdvertiser.startAdvertising(settings, data, advertiseCallback); 153 mAdvertiserStartCount = 0; 154 } else if (mAdvertiserStartCount < BLE_RETRY_LIMIT) { 155 mHandler.postDelayed( 156 () -> startAdvertisingInternally(settings, data, advertiseCallback), 157 BLE_RETRY_INTERVAL_MS); 158 mAdvertiserStartCount += 1; 159 } else { 160 Log.e(TAG, "Cannot start BLE Advertisement. BT Adapter: " 161 + BluetoothAdapter.getDefaultAdapter() + " Advertise Retry count: " 162 + mAdvertiserStartCount); 163 } 164 } 165 stopAdvertising(AdvertiseCallback advertiseCallback)166 protected void stopAdvertising(AdvertiseCallback advertiseCallback) { 167 if (mAdvertiser != null) { 168 if (Log.isLoggable(TAG, Log.DEBUG)) { 169 Log.d(TAG, "stopAdvertising: "); 170 } 171 mAdvertiser.stopAdvertising(advertiseCallback); 172 } 173 } 174 175 /** 176 * Notifies the characteristic change via {@link BluetoothGattServer} 177 */ notifyCharacteristicChanged(BluetoothDevice device, BluetoothGattCharacteristic characteristic, boolean confirm)178 protected void notifyCharacteristicChanged(BluetoothDevice device, 179 BluetoothGattCharacteristic characteristic, boolean confirm) { 180 if (mGattServer == null) { 181 return; 182 } 183 184 boolean result = mGattServer.notifyCharacteristicChanged(device, characteristic, confirm); 185 186 if (Log.isLoggable(TAG, Log.DEBUG)) { 187 Log.d(TAG, "notifyCharacteristicChanged succeeded: " + result); 188 } 189 } 190 191 /** 192 * Connect the Gatt server of the remote device to retrieve device name. 193 */ retrieveDeviceName(BluetoothDevice device)194 protected final void retrieveDeviceName(BluetoothDevice device) { 195 mBluetoothGatt = device.connectGatt(getContext(), false, mGattCallback); 196 } 197 getContext()198 protected Context getContext() { 199 return mContext; 200 } 201 202 /** 203 * Cleans up the BLE GATT server state. 204 */ cleanup()205 void cleanup() { 206 // Stops the advertiser and GATT server. This needs to be done to avoid leaks 207 if (mAdvertiser != null) { 208 mAdvertiser.cleanup(); 209 } 210 211 if (mGattServer != null) { 212 mGattServer.clearServices(); 213 try { 214 for (BluetoothDevice d : mBluetoothManager.getConnectedDevices(GATT_SERVER)) { 215 mGattServer.cancelConnection(d); 216 } 217 } catch (UnsupportedOperationException e) { 218 Log.e(TAG, "Error getting connected devices", e); 219 } finally { 220 stopGattServer(); 221 } 222 } 223 } 224 225 /** 226 * Close the GATT Server 227 */ stopGattServer()228 void stopGattServer() { 229 if (mGattServer == null) { 230 return; 231 } 232 if (Log.isLoggable(TAG, Log.DEBUG)) { 233 Log.d(TAG, "stopGattServer"); 234 } 235 if (mBluetoothGatt != null) { 236 mBluetoothGatt.disconnect(); 237 } 238 mGattServer.close(); 239 mGattServer = null; 240 } 241 242 /** 243 * Triggered when the name of the remote device is retrieved. 244 * 245 * @param deviceName Name of the remote device. 246 */ onDeviceNameRetrieved(@ullable String deviceName)247 protected void onDeviceNameRetrieved(@Nullable String deviceName) { 248 } 249 250 /** 251 * Triggered if a remote client has requested to change the MTU for a given connection. 252 * 253 * @param size The new MTU size. 254 */ onMtuSizeChanged(int size)255 protected void onMtuSizeChanged(int size) { 256 } 257 258 /** 259 * Triggered when a device (GATT client) connected. 260 * 261 * @param device Remote device that connected on BLE. 262 */ onRemoteDeviceConnected(BluetoothDevice device)263 protected void onRemoteDeviceConnected(BluetoothDevice device) { 264 } 265 266 /** 267 * Triggered when a device (GATT client) disconnected. 268 * 269 * @param device Remote device that disconnected on BLE. 270 */ onRemoteDeviceDisconnected(BluetoothDevice device)271 protected void onRemoteDeviceDisconnected(BluetoothDevice device) { 272 } 273 274 /** 275 * Triggered when this BleManager receives a write request from a remote 276 * device. Sub-classes should implement how to handle requests. 277 * <p> 278 * 279 * @see BluetoothGattServerCallback#onCharacteristicWriteRequest(BluetoothDevice, int, 280 * BluetoothGattCharacteristic, boolean, boolean, int, byte[]) 281 */ onCharacteristicWrite(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value)282 protected abstract void onCharacteristicWrite(BluetoothDevice device, int requestId, 283 BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean 284 responseNeeded, int offset, byte[] value); 285 286 /** 287 * Triggered when this BleManager receives a read request from a remote device. 288 * <p> 289 * 290 * @see BluetoothGattServerCallback#onCharacteristicReadRequest(BluetoothDevice, int, int, 291 * BluetoothGattCharacteristic) 292 */ onCharacteristicRead(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic)293 protected abstract void onCharacteristicRead(BluetoothDevice device, 294 int requestId, int offset, BluetoothGattCharacteristic characteristic); 295 296 private final BluetoothGattServerCallback mGattServerCallback = 297 new BluetoothGattServerCallback() { 298 @Override 299 public void onConnectionStateChange(BluetoothDevice device, int status, 300 int newState) { 301 if (Log.isLoggable(TAG, Log.DEBUG)) { 302 Log.d(TAG, "BLE Connection State Change: " + newState); 303 } 304 switch (newState) { 305 case BluetoothProfile.STATE_CONNECTED: 306 onRemoteDeviceConnected(device); 307 break; 308 case BluetoothProfile.STATE_DISCONNECTED: 309 onRemoteDeviceDisconnected(device); 310 break; 311 default: 312 Log.w(TAG, 313 "Connection state not connecting or disconnecting; ignoring: " 314 + newState); 315 } 316 } 317 318 @Override 319 public void onServiceAdded(int status, BluetoothGattService service) { 320 if (Log.isLoggable(TAG, Log.DEBUG)) { 321 Log.d(TAG, 322 "Service added status: " + status + " uuid: " + service.getUuid()); 323 } 324 } 325 326 @Override 327 public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, 328 int offset, BluetoothGattCharacteristic characteristic) { 329 if (Log.isLoggable(TAG, Log.DEBUG)) { 330 Log.d(TAG, "Read request for characteristic: " + characteristic.getUuid()); 331 } 332 333 mGattServer.sendResponse(device, requestId, 334 BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue()); 335 onCharacteristicRead(device, requestId, offset, characteristic); 336 } 337 338 @Override 339 public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, 340 BluetoothGattCharacteristic characteristic, boolean preparedWrite, 341 boolean responseNeeded, int offset, byte[] value) { 342 if (Log.isLoggable(TAG, Log.DEBUG)) { 343 Log.d(TAG, "Write request for characteristic: " + characteristic.getUuid() 344 + "value: " + Utils.byteArrayToHexString(value)); 345 } 346 347 mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 348 offset, value); 349 onCharacteristicWrite(device, requestId, characteristic, 350 preparedWrite, responseNeeded, offset, value); 351 } 352 353 @Override 354 public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, 355 BluetoothGattDescriptor descriptor, boolean preparedWrite, 356 boolean responseNeeded, int offset, byte[] value) { 357 if (Log.isLoggable(TAG, Log.DEBUG)) { 358 Log.d(TAG, "Write request for descriptor: " + descriptor.getUuid() 359 + "; value: " + Utils.byteArrayToHexString(value)); 360 } 361 362 mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 363 offset, value); 364 } 365 366 @Override 367 public void onMtuChanged(BluetoothDevice device, int mtu) { 368 if (Log.isLoggable(TAG, Log.DEBUG)) { 369 Log.d(TAG, "onMtuChanged: " + mtu + " for device " + device.getAddress()); 370 } 371 onMtuSizeChanged(mtu); 372 } 373 374 }; 375 376 private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { 377 @Override 378 public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 379 if (Log.isLoggable(TAG, Log.DEBUG)) { 380 Log.d(TAG, "Gatt Connection State Change: " + newState); 381 } 382 switch (newState) { 383 case BluetoothProfile.STATE_CONNECTED: 384 if (Log.isLoggable(TAG, Log.DEBUG)) { 385 Log.d(TAG, "Gatt connected"); 386 } 387 mBluetoothGatt.discoverServices(); 388 break; 389 case BluetoothProfile.STATE_DISCONNECTED: 390 if (Log.isLoggable(TAG, Log.DEBUG)) { 391 Log.d(TAG, "Gatt Disconnected"); 392 } 393 break; 394 default: 395 if (Log.isLoggable(TAG, Log.DEBUG)) { 396 Log.d(TAG, 397 "Connection state not connecting or disconnecting; ignoring: " 398 + newState); 399 } 400 } 401 } 402 403 @Override 404 public void onServicesDiscovered(BluetoothGatt gatt, int status) { 405 if (Log.isLoggable(TAG, Log.DEBUG)) { 406 Log.d(TAG, "Gatt Services Discovered"); 407 } 408 BluetoothGattService gapService = mBluetoothGatt.getService( 409 GENERIC_ACCESS_PROFILE_UUID); 410 if (gapService == null) { 411 Log.e(TAG, "Generic Access Service is Null"); 412 return; 413 } 414 BluetoothGattCharacteristic deviceNameCharacteristic = gapService.getCharacteristic( 415 DEVICE_NAME_UUID); 416 if (deviceNameCharacteristic == null) { 417 Log.e(TAG, "Device Name Characteristic is Null"); 418 return; 419 } 420 mBluetoothGatt.readCharacteristic(deviceNameCharacteristic); 421 } 422 423 @Override 424 public void onCharacteristicRead(BluetoothGatt gatt, 425 BluetoothGattCharacteristic characteristic, int status) { 426 if (status == BluetoothGatt.GATT_SUCCESS) { 427 String deviceName = characteristic.getStringValue(0); 428 if (Log.isLoggable(TAG, Log.DEBUG)) { 429 Log.d(TAG, "BLE Device Name: " + deviceName); 430 } 431 onDeviceNameRetrieved(deviceName); 432 } else { 433 Log.e(TAG, "Reading GAP Failed: " + status); 434 } 435 } 436 }; 437 } 438