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