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