1 /* 2 * Copyright (C) 2022 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.pandora 18 19 import android.bluetooth.BluetoothDevice 20 import android.bluetooth.BluetoothGattCharacteristic 21 import android.bluetooth.BluetoothGattDescriptor 22 import android.bluetooth.BluetoothGattService 23 import android.bluetooth.BluetoothGattService.SERVICE_TYPE_PRIMARY 24 import android.bluetooth.BluetoothManager 25 import android.content.Context 26 import android.content.Intent 27 import android.content.IntentFilter 28 import android.util.Log 29 import io.grpc.stub.StreamObserver 30 import java.io.Closeable 31 import java.util.UUID 32 import kotlinx.coroutines.CoroutineScope 33 import kotlinx.coroutines.Dispatchers 34 import kotlinx.coroutines.cancel 35 import kotlinx.coroutines.delay 36 import kotlinx.coroutines.flow.Flow 37 import kotlinx.coroutines.flow.SharingStarted 38 import kotlinx.coroutines.flow.filter 39 import kotlinx.coroutines.flow.first 40 import kotlinx.coroutines.flow.shareIn 41 import kotlinx.coroutines.flow.take 42 import pandora.GATTGrpc.GATTImplBase 43 import pandora.GattProto.* 44 45 @kotlinx.coroutines.ExperimentalCoroutinesApi 46 class Gatt(private val context: Context) : GATTImplBase(), Closeable { 47 private val TAG = "PandoraGatt" 48 49 private val mScope: CoroutineScope = CoroutineScope(Dispatchers.Default.limitedParallelism(1)) 50 51 private val mBluetoothManager = context.getSystemService(BluetoothManager::class.java)!! 52 private val mBluetoothAdapter = mBluetoothManager.adapter 53 <lambda>null54 private val serverManager by lazy { GattServerManager(mBluetoothManager, context, mScope) } 55 56 private val flow: Flow<Intent> 57 58 init { 59 val intentFilter = IntentFilter() 60 intentFilter.addAction(BluetoothDevice.ACTION_UUID) 61 62 flow = intentFlow(context, intentFilter, mScope).shareIn(mScope, SharingStarted.Eagerly) 63 } 64 closenull65 override fun close() { 66 serverManager.server.close() 67 mScope.cancel() 68 // Clear existing Gatt instances to fix flakiness: b/279599889 69 GattInstance.clearAllInstances() 70 } 71 exchangeMTUnull72 override fun exchangeMTU( 73 request: ExchangeMTURequest, 74 responseObserver: StreamObserver<ExchangeMTUResponse>, 75 ) { 76 grpcUnary<ExchangeMTUResponse>(mScope, responseObserver) { 77 val mtu = request.mtu 78 Log.i(TAG, "exchangeMTU MTU=$mtu") 79 if (!GattInstance.get(request.connection.address).mGatt.requestMtu(mtu)) { 80 throw RuntimeException("Error on requesting MTU $mtu") 81 } 82 ExchangeMTUResponse.newBuilder().build() 83 } 84 } 85 writeAttFromHandlenull86 override fun writeAttFromHandle( 87 request: WriteRequest, 88 responseObserver: StreamObserver<WriteResponse>, 89 ) { 90 grpcUnary<WriteResponse>(mScope, responseObserver) { 91 Log.i(TAG, "writeAttFromHandle handle=${request.handle}") 92 val gattInstance = GattInstance.get(request.connection.address) 93 var characteristic: BluetoothGattCharacteristic? = 94 getCharacteristicWithHandle(request.handle, gattInstance) 95 if (characteristic == null) { 96 val descriptor: BluetoothGattDescriptor? = 97 getDescriptorWithHandle(request.handle, gattInstance) 98 checkNotNull(descriptor) { 99 "Found no characteristic or descriptor with handle ${request.handle}" 100 } 101 val valueWrote = 102 gattInstance.writeDescriptorBlocking(descriptor, request.value.toByteArray()) 103 WriteResponse.newBuilder() 104 .setHandle(valueWrote.handle) 105 .setStatus(valueWrote.status) 106 .build() 107 } else { 108 val valueWrote = 109 gattInstance.writeCharacteristicBlocking( 110 characteristic, 111 request.value.toByteArray(), 112 ) 113 WriteResponse.newBuilder() 114 .setHandle(valueWrote.handle) 115 .setStatus(valueWrote.status) 116 .build() 117 } 118 } 119 } 120 discoverServiceByUuidnull121 override fun discoverServiceByUuid( 122 request: DiscoverServiceByUuidRequest, 123 responseObserver: StreamObserver<DiscoverServicesResponse>, 124 ) { 125 grpcUnary<DiscoverServicesResponse>(mScope, responseObserver) { 126 val gattInstance = GattInstance.get(request.connection.address) 127 Log.i(TAG, "discoverServiceByUuid uuid=${request.uuid}") 128 // In some cases, GATT starts a discovery immediately after being connected, so 129 // we need to wait until the service discovery is finished to be able to discover again. 130 // This takes between 20s and 28s, and there is no way to know if the service is busy or 131 // not. 132 // Delay was originally 30s, but due to flakiness increased to 32s. 133 delay(32000L) 134 check(gattInstance.mGatt.discoverServiceByUuid(UUID.fromString(request.uuid))) 135 // BluetoothGatt#discoverServiceByUuid does not trigger any callback and does not return 136 // any service, the API was made for PTS testing only. 137 DiscoverServicesResponse.newBuilder().build() 138 } 139 } 140 discoverServicesnull141 override fun discoverServices( 142 request: DiscoverServicesRequest, 143 responseObserver: StreamObserver<DiscoverServicesResponse>, 144 ) { 145 grpcUnary<DiscoverServicesResponse>(mScope, responseObserver) { 146 Log.i(TAG, "discoverServices") 147 val gattInstance = GattInstance.get(request.connection.address) 148 check(gattInstance.mGatt.discoverServices()) 149 gattInstance.waitForDiscoveryEnd() 150 DiscoverServicesResponse.newBuilder() 151 .addAllServices(generateServicesList(gattInstance.mGatt.services, 1)) 152 .build() 153 } 154 } 155 discoverServicesSdpnull156 override fun discoverServicesSdp( 157 request: DiscoverServicesSdpRequest, 158 responseObserver: StreamObserver<DiscoverServicesSdpResponse>, 159 ) { 160 grpcUnary<DiscoverServicesSdpResponse>(mScope, responseObserver) { 161 Log.i(TAG, "discoverServicesSdp") 162 val bluetoothDevice = request.address.toBluetoothDevice(mBluetoothAdapter) 163 check(bluetoothDevice.fetchUuidsWithSdp()) 164 // Several ACTION_UUID could be sent and some of them are empty (null) 165 flow 166 .filter { it.getAction() == BluetoothDevice.ACTION_UUID } 167 .filter { it.getBluetoothDeviceExtra() == bluetoothDevice } 168 .take(2) 169 .filter { bluetoothDevice.getUuids() != null } 170 .first() 171 val uuidsList = arrayListOf<String>() 172 for (parcelUuid in bluetoothDevice.getUuids()) { 173 uuidsList.add(parcelUuid.toString().uppercase()) 174 } 175 DiscoverServicesSdpResponse.newBuilder().addAllServiceUuids(uuidsList).build() 176 } 177 } 178 clearCachenull179 override fun clearCache( 180 request: ClearCacheRequest, 181 responseObserver: StreamObserver<ClearCacheResponse>, 182 ) { 183 grpcUnary<ClearCacheResponse>(mScope, responseObserver) { 184 Log.i(TAG, "clearCache") 185 val gattInstance = GattInstance.get(request.connection.address) 186 check(gattInstance.mGatt.refresh()) 187 ClearCacheResponse.newBuilder().build() 188 } 189 } 190 readCharacteristicFromHandlenull191 override fun readCharacteristicFromHandle( 192 request: ReadCharacteristicRequest, 193 responseObserver: StreamObserver<ReadCharacteristicResponse>, 194 ) { 195 grpcUnary<ReadCharacteristicResponse>(mScope, responseObserver) { 196 Log.i(TAG, "readCharacteristicFromHandle handle=${request.handle}") 197 val gattInstance = GattInstance.get(request.connection.address) 198 val characteristic: BluetoothGattCharacteristic? = 199 getCharacteristicWithHandle(request.handle, gattInstance) 200 checkNotNull(characteristic) { "Characteristic handle ${request.handle} not found." } 201 val readValue = gattInstance.readCharacteristicBlocking(characteristic) 202 ReadCharacteristicResponse.newBuilder() 203 .setValue( 204 AttValue.newBuilder().setHandle(readValue.handle).setValue(readValue.value) 205 ) 206 .setStatus(readValue.status) 207 .build() 208 } 209 } 210 readCharacteristicsFromUuidnull211 override fun readCharacteristicsFromUuid( 212 request: ReadCharacteristicsFromUuidRequest, 213 responseObserver: StreamObserver<ReadCharacteristicsFromUuidResponse>, 214 ) { 215 grpcUnary<ReadCharacteristicsFromUuidResponse>(mScope, responseObserver) { 216 Log.i(TAG, "readCharacteristicsFromUuid uuid=${request.uuid}") 217 val gattInstance = GattInstance.get(request.connection.address) 218 tryDiscoverServices(gattInstance) 219 val readValues = 220 gattInstance.readCharacteristicUuidBlocking( 221 UUID.fromString(request.uuid), 222 request.startHandle, 223 request.endHandle, 224 ) 225 ReadCharacteristicsFromUuidResponse.newBuilder() 226 .addAllCharacteristicsRead(generateReadValuesList(readValues)) 227 .build() 228 } 229 } 230 readCharacteristicDescriptorFromHandlenull231 override fun readCharacteristicDescriptorFromHandle( 232 request: ReadCharacteristicDescriptorRequest, 233 responseObserver: StreamObserver<ReadCharacteristicDescriptorResponse>, 234 ) { 235 grpcUnary<ReadCharacteristicDescriptorResponse>(mScope, responseObserver) { 236 Log.i(TAG, "readCharacteristicDescriptorFromHandle handle=${request.handle}") 237 val gattInstance = GattInstance.get(request.connection.address) 238 val descriptor: BluetoothGattDescriptor? = 239 getDescriptorWithHandle(request.handle, gattInstance) 240 checkNotNull(descriptor) { "Descriptor handle ${request.handle} not found." } 241 val readValue = gattInstance.readDescriptorBlocking(descriptor) 242 ReadCharacteristicDescriptorResponse.newBuilder() 243 .setValue( 244 AttValue.newBuilder().setHandle(readValue.handle).setValue(readValue.value) 245 ) 246 .setStatus(readValue.status) 247 .build() 248 } 249 } 250 registerServicenull251 override fun registerService( 252 request: RegisterServiceRequest, 253 responseObserver: StreamObserver<RegisterServiceResponse>, 254 ) { 255 grpcUnary(mScope, responseObserver) { 256 Log.i(TAG, "registerService") 257 val service = 258 BluetoothGattService(UUID.fromString(request.service.uuid), SERVICE_TYPE_PRIMARY) 259 for (characteristic_params in request.service.characteristicsList) { 260 val characteristic = 261 BluetoothGattCharacteristic( 262 UUID.fromString(characteristic_params.uuid), 263 characteristic_params.properties, 264 characteristic_params.permissions, 265 ) 266 for (descriptor_params in characteristic_params.descriptorsList) { 267 characteristic.addDescriptor( 268 BluetoothGattDescriptor( 269 UUID.fromString(descriptor_params.uuid), 270 descriptor_params.properties, 271 descriptor_params.permissions, 272 ) 273 ) 274 } 275 service.addCharacteristic(characteristic) 276 } 277 278 serverManager.server.addService(service) 279 val addedService = serverManager.serviceFlow.first() 280 281 RegisterServiceResponse.newBuilder() 282 .setService( 283 GattService.newBuilder() 284 .setHandle(addedService.instanceId) 285 .setServiceType(ServiceType.forNumber(addedService.type)) 286 .setUuid(addedService.uuid.toString().uppercase()) 287 .addAllIncludedServices(generateServicesList(service.includedServices, 1)) 288 .addAllCharacteristics(generateCharacteristicsList(service.characteristics)) 289 .build() 290 ) 291 .build() 292 } 293 } 294 setCharacteristicNotificationFromHandlenull295 override fun setCharacteristicNotificationFromHandle( 296 request: SetCharacteristicNotificationFromHandleRequest, 297 responseObserver: StreamObserver<SetCharacteristicNotificationFromHandleResponse>, 298 ) { 299 grpcUnary<SetCharacteristicNotificationFromHandleResponse>(mScope, responseObserver) { 300 Log.i(TAG, "SetCharcteristicNotificationFromHandle") 301 val gattInstance = GattInstance.get(request.connection.address) 302 val descriptor: BluetoothGattDescriptor? = 303 getDescriptorWithHandle(request.handle, gattInstance) 304 checkNotNull(descriptor) { "Found no descriptor with handle ${request.handle}" } 305 var characteristic = descriptor.getCharacteristic() 306 gattInstance.mGatt.setCharacteristicNotification(characteristic, true) 307 if (request.enableValue == EnableValue.ENABLE_INDICATION_VALUE) { 308 val valueWrote = 309 gattInstance.writeDescriptorBlocking( 310 descriptor, 311 BluetoothGattDescriptor.ENABLE_INDICATION_VALUE, 312 ) 313 SetCharacteristicNotificationFromHandleResponse.newBuilder() 314 .setHandle(valueWrote.handle) 315 .setStatus(valueWrote.status) 316 .build() 317 } else { 318 val valueWrote = 319 gattInstance.writeDescriptorBlocking( 320 descriptor, 321 BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE, 322 ) 323 SetCharacteristicNotificationFromHandleResponse.newBuilder() 324 .setHandle(valueWrote.handle) 325 .setStatus(valueWrote.status) 326 .build() 327 } 328 } 329 } 330 waitCharacteristicNotificationnull331 override fun waitCharacteristicNotification( 332 request: WaitCharacteristicNotificationRequest, 333 responseObserver: StreamObserver<WaitCharacteristicNotificationResponse>, 334 ) { 335 grpcUnary<WaitCharacteristicNotificationResponse>(mScope, responseObserver) { 336 val gattInstance = GattInstance.get(request.connection.address) 337 val descriptor: BluetoothGattDescriptor? = 338 getDescriptorWithHandle(request.handle, gattInstance) 339 checkNotNull(descriptor) { "Found no descriptor with handle ${request.handle}" } 340 var characteristic = descriptor.getCharacteristic() 341 val characteristicNotificationReceived = 342 gattInstance.waitForOnCharacteristicChanged(characteristic) 343 WaitCharacteristicNotificationResponse.newBuilder() 344 .setCharacteristicNotificationReceived(characteristicNotificationReceived) 345 .build() 346 } 347 } 348 349 /** 350 * Discovers services, then returns characteristic with given handle. BluetoothGatt API is 351 * package-private so we have to redefine it here. 352 */ getCharacteristicWithHandlenull353 private suspend fun getCharacteristicWithHandle( 354 handle: Int, 355 gattInstance: GattInstance, 356 ): BluetoothGattCharacteristic? { 357 tryDiscoverServices(gattInstance) 358 for (service: BluetoothGattService in gattInstance.mGatt.services.orEmpty()) { 359 for (characteristic: BluetoothGattCharacteristic in service.characteristics) { 360 if (characteristic.instanceId == handle) { 361 return characteristic 362 } 363 } 364 } 365 return null 366 } 367 368 /** 369 * Discovers services, then returns descriptor with given handle. BluetoothGatt API is 370 * package-private so we have to redefine it here. 371 */ getDescriptorWithHandlenull372 private suspend fun getDescriptorWithHandle( 373 handle: Int, 374 gattInstance: GattInstance, 375 ): BluetoothGattDescriptor? { 376 tryDiscoverServices(gattInstance) 377 for (service: BluetoothGattService in gattInstance.mGatt.services.orEmpty()) { 378 for (characteristic: BluetoothGattCharacteristic in service.characteristics) { 379 for (descriptor: BluetoothGattDescriptor in characteristic.descriptors) { 380 if (descriptor.getInstanceId() == handle) { 381 return descriptor 382 } 383 } 384 } 385 } 386 return null 387 } 388 389 /** Generates a list of GattService from a list of BluetoothGattService. */ generateServicesListnull390 private fun generateServicesList( 391 servicesList: List<BluetoothGattService>, 392 dpth: Int, 393 ): ArrayList<GattService> { 394 val newServicesList = arrayListOf<GattService>() 395 for (service in servicesList) { 396 val serviceBuilder = 397 GattService.newBuilder() 398 .setHandle(service.getInstanceId()) 399 .setServiceType(ServiceType.forNumber(service.type)) 400 .setUuid(service.getUuid().toString().uppercase()) 401 .addAllIncludedServices( 402 generateServicesList(service.getIncludedServices(), dpth + 1) 403 ) 404 .addAllCharacteristics(generateCharacteristicsList(service.characteristics)) 405 newServicesList.add(serviceBuilder.build()) 406 } 407 return newServicesList 408 } 409 410 /** Generates a list of GattCharacteristic from a list of BluetoothGattCharacteristic. */ generateCharacteristicsListnull411 private fun generateCharacteristicsList( 412 characteristicsList: List<BluetoothGattCharacteristic> 413 ): ArrayList<GattCharacteristic> { 414 val newCharacteristicsList = arrayListOf<GattCharacteristic>() 415 for (characteristic in characteristicsList) { 416 val characteristicBuilder = 417 GattCharacteristic.newBuilder() 418 .setProperties(characteristic.getProperties()) 419 .setPermissions(characteristic.getPermissions()) 420 .setUuid(characteristic.getUuid().toString().uppercase()) 421 .addAllDescriptors(generateDescriptorsList(characteristic.getDescriptors())) 422 .setHandle(characteristic.getInstanceId()) 423 newCharacteristicsList.add(characteristicBuilder.build()) 424 } 425 return newCharacteristicsList 426 } 427 428 /** Generates a list of GattCharacteristicDescriptor from a list of BluetoothGattDescriptor. */ generateDescriptorsListnull429 private fun generateDescriptorsList( 430 descriptorsList: List<BluetoothGattDescriptor> 431 ): ArrayList<GattCharacteristicDescriptor> { 432 val newDescriptorsList = arrayListOf<GattCharacteristicDescriptor>() 433 for (descriptor in descriptorsList) { 434 val descriptorBuilder = 435 GattCharacteristicDescriptor.newBuilder() 436 .setHandle(descriptor.getInstanceId()) 437 .setPermissions(descriptor.getPermissions()) 438 .setUuid(descriptor.getUuid().toString().uppercase()) 439 newDescriptorsList.add(descriptorBuilder.build()) 440 } 441 return newDescriptorsList 442 } 443 444 /** Generates a list of ReadCharacteristicResponse from a list of GattInstanceValueRead. */ generateReadValuesListnull445 private fun generateReadValuesList( 446 readValuesList: ArrayList<GattInstance.GattInstanceValueRead> 447 ): ArrayList<ReadCharacteristicResponse> { 448 val newReadValuesList = arrayListOf<ReadCharacteristicResponse>() 449 for (readValue in readValuesList) { 450 val readValueBuilder = 451 ReadCharacteristicResponse.newBuilder() 452 .setValue( 453 AttValue.newBuilder().setHandle(readValue.handle).setValue(readValue.value) 454 ) 455 .setStatus(readValue.status) 456 newReadValuesList.add(readValueBuilder.build()) 457 } 458 return newReadValuesList 459 } 460 tryDiscoverServicesnull461 private suspend fun tryDiscoverServices(gattInstance: GattInstance) { 462 if (!gattInstance.servicesDiscovered() && !gattInstance.mGatt.discoverServices()) { 463 throw RuntimeException("Error on discovering services for $gattInstance") 464 } else { 465 gattInstance.waitForDiscoveryEnd() 466 } 467 } 468 } 469