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