• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.BluetoothGatt
21 import android.bluetooth.BluetoothGattCallback
22 import android.bluetooth.BluetoothGattCharacteristic
23 import android.bluetooth.BluetoothGattDescriptor
24 import android.bluetooth.BluetoothGattService
25 import android.bluetooth.BluetoothProfile
26 import android.bluetooth.BluetoothStatusCodes
27 import android.content.Context
28 import android.util.Log
29 import com.google.protobuf.ByteString
30 import java.util.UUID
31 import kotlinx.coroutines.flow.MutableStateFlow
32 import kotlinx.coroutines.flow.first
33 import pandora.GattProto.*
34 
35 /** GattInstance extends and simplifies Android GATT APIs without re-implementing them. */
36 @kotlinx.coroutines.ExperimentalCoroutinesApi
37 class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mContext: Context) {
38   private val TAG = "GattInstance"
39   public val mGatt: BluetoothGatt
40 
41   private var mServiceDiscovered = MutableStateFlow(false)
42   private var mConnectionState = MutableStateFlow(BluetoothProfile.STATE_DISCONNECTED)
43   private var mValuesRead = MutableStateFlow(0)
44   private var mValueWrote = MutableStateFlow(false)
45   private var mOnCharacteristicChanged = MutableStateFlow(false)
46   private var mCharacteristicChangedMap : MutableMap<BluetoothGattCharacteristic, Boolean> = mutableMapOf()
47 
48   /**
49    * Wrapper for characteristic and descriptor reading. Uuid, startHandle and endHandle are used to
50    * compare with the callback returned object. Value and status can be read once the read has been
51    * done. ByteString and AttStatusCode are used to ensure compatibility with proto.
52    */
53   class GattInstanceValueRead(
54     var uuid: UUID?,
55     var handle: Int,
56     var value: ByteString?,
57     var status: AttStatusCode
58   ) {}
59   private var mGattInstanceValuesRead = arrayListOf<GattInstanceValueRead>()
60 
61   class GattInstanceValueWrote(var uuid: UUID?, var handle: Int, var status: AttStatusCode) {}
62   private var mGattInstanceValueWrote = GattInstanceValueWrote(null, 0, AttStatusCode.UNKNOWN_ERROR)
63 
64   companion object GattManager {
65     val gattInstances: MutableMap<String, GattInstance> = mutableMapOf<String, GattInstance>()
getnull66     fun get(address: String): GattInstance {
67       val instance = gattInstances.get(address)
68       requireNotNull(instance) { "Unable to find GATT instance for $address" }
69       return instance
70     }
getnull71     fun get(address: ByteString): GattInstance {
72       val instance = gattInstances.get(address.toByteArray().decodeToString())
73       requireNotNull(instance) { "Unable to find GATT instance for $address" }
74       return instance
75     }
clearAllInstancesnull76     fun clearAllInstances() {
77         gattInstances.clear()
78     }
79   }
80 
81   private val mCallback =
82     object : BluetoothGattCallback() {
onConnectionStateChangenull83       override fun onConnectionStateChange(
84         bluetoothGatt: BluetoothGatt,
85         status: Int,
86         newState: Int
87       ) {
88         Log.i(TAG, "$mDevice connection state changed to $newState")
89         mConnectionState.value = newState
90         if (newState == BluetoothProfile.STATE_DISCONNECTED) {
91           gattInstances.remove(mDevice.address)
92         }
93       }
94 
onServicesDiscoverednull95       override fun onServicesDiscovered(bluetoothGatt: BluetoothGatt, status: Int) {
96         if (status == BluetoothGatt.GATT_SUCCESS) {
97           Log.i(TAG, "Services have been discovered for $mDevice")
98           mServiceDiscovered.value = true
99         }
100       }
101 
onCharacteristicReadnull102       override fun onCharacteristicRead(
103         bluetoothGatt: BluetoothGatt,
104         characteristic: BluetoothGattCharacteristic,
105         value: ByteArray,
106         status: Int
107       ) {
108         Log.i(TAG, "onCharacteristicRead, status: $status")
109         for (gattInstanceValueRead: GattInstanceValueRead in mGattInstanceValuesRead) {
110           if (
111             characteristic.getUuid() == gattInstanceValueRead.uuid &&
112               characteristic.getInstanceId() == gattInstanceValueRead.handle
113           ) {
114             gattInstanceValueRead.value = ByteString.copyFrom(value)
115             gattInstanceValueRead.status = AttStatusCode.forNumber(status)
116             mValuesRead.value++
117           }
118         }
119       }
120 
onDescriptorReadnull121       override fun onDescriptorRead(
122         bluetoothGatt: BluetoothGatt,
123         descriptor: BluetoothGattDescriptor,
124         status: Int,
125         value: ByteArray
126       ) {
127         Log.i(TAG, "onDescriptorRead, status: $status")
128         for (gattInstanceValueRead: GattInstanceValueRead in mGattInstanceValuesRead) {
129           if (
130             descriptor.getUuid() == gattInstanceValueRead.uuid &&
131               descriptor.getInstanceId() >= gattInstanceValueRead.handle
132           ) {
133             gattInstanceValueRead.value = ByteString.copyFrom(value)
134             gattInstanceValueRead.status = AttStatusCode.forNumber(status)
135             mValuesRead.value++
136           }
137         }
138       }
139 
onCharacteristicWritenull140       override fun onCharacteristicWrite(
141         bluetoothGatt: BluetoothGatt,
142         characteristic: BluetoothGattCharacteristic,
143         status: Int
144       ) {
145         Log.i(TAG, "onCharacteristicWrite, status: $status")
146         mGattInstanceValueWrote.status = AttStatusCode.forNumber(status)
147         mValueWrote.value = true
148       }
149 
onDescriptorWritenull150       override fun onDescriptorWrite(
151         bluetoothGatt: BluetoothGatt,
152         descriptor: BluetoothGattDescriptor,
153         status: Int
154       ) {
155         Log.i(TAG, "onDescriptorWrite, status: $status")
156         mGattInstanceValueWrote.status = AttStatusCode.forNumber(status)
157         mValueWrote.value = true
158       }
159 
onCharacteristicChangednull160       override fun onCharacteristicChanged(
161         bluetoothGatt: BluetoothGatt,
162         characteristic: BluetoothGattCharacteristic,
163         value: ByteArray
164       ) {
165         Log.i(TAG, "onCharacteristicChanged, characteristic: " + characteristic.getUuid().toString().uppercase())
166         mCharacteristicChangedMap[characteristic] = true
167         mOnCharacteristicChanged.value = true
168       }
169     }
170 
171   init {
172     if (!isBLETransport()) {
<lambda>null173       require(isBonded()) { "Trying to connect non BLE GATT on a not bonded device $mDevice" }
174     }
<lambda>null175     require(gattInstances.get(mDevice.address) == null) {
176       "Trying to connect GATT on an already connected device $mDevice"
177     }
178 
179     mGatt = mDevice.connectGatt(mContext, false, mCallback, mTransport)
180 
<lambda>null181     checkNotNull(mGatt) { "Failed to connect GATT on $mDevice" }
182     gattInstances.put(mDevice.address, this)
183   }
184 
isConnectednull185   public fun isConnected(): Boolean {
186     return mConnectionState.value == BluetoothProfile.STATE_CONNECTED
187   }
188 
isDisconnectednull189   public fun isDisconnected(): Boolean {
190     return mConnectionState.value == BluetoothProfile.STATE_DISCONNECTED
191   }
192 
isBondednull193   public fun isBonded(): Boolean {
194     return mDevice.getBondState() == BluetoothDevice.BOND_BONDED
195   }
196 
isBLETransportnull197   public fun isBLETransport(): Boolean {
198     return mTransport == BluetoothDevice.TRANSPORT_LE
199   }
200 
servicesDiscoverednull201   public fun servicesDiscovered(): Boolean {
202     return mServiceDiscovered.value
203   }
204 
waitForOnCharacteristicChangednull205   public suspend fun waitForOnCharacteristicChanged(
206     characteristic: BluetoothGattCharacteristic
207     ):  Boolean{
208     if (mOnCharacteristicChanged.value == false) {
209       mOnCharacteristicChanged.first { it  == true }
210     }
211     return mCharacteristicChangedMap[characteristic] == true
212   }
213 
waitForStatenull214   public suspend fun waitForState(newState: Int) {
215     if (mConnectionState.value != newState) {
216       mConnectionState.first { it == newState }
217     }
218   }
219 
waitForDiscoveryEndnull220   public suspend fun waitForDiscoveryEnd() {
221     if (mServiceDiscovered.value != true) {
222       mServiceDiscovered.first { it == true }
223     }
224   }
225 
waitForValuesReadEndnull226   public suspend fun waitForValuesReadEnd() {
227     if (mValuesRead.value < mGattInstanceValuesRead.size) {
228       mValuesRead.first { it == mGattInstanceValuesRead.size }
229     }
230     mValuesRead.value = 0
231   }
232 
waitForValuesReadnull233   public suspend fun waitForValuesRead() {
234     if (mValuesRead.value < mGattInstanceValuesRead.size) {
235       mValuesRead.first { it == mGattInstanceValuesRead.size }
236     }
237   }
238 
waitForWriteEndnull239   public suspend fun waitForWriteEnd() {
240     if (mValueWrote.value != true) {
241       mValueWrote.first { it == true }
242     }
243     mValueWrote.value = false
244   }
245 
readCharacteristicBlockingnull246   public suspend fun readCharacteristicBlocking(
247     characteristic: BluetoothGattCharacteristic
248   ): GattInstanceValueRead {
249     // Init mGattInstanceValuesRead with characteristic values.
250     mGattInstanceValuesRead =
251       arrayListOf(
252         GattInstanceValueRead(
253           characteristic.getUuid(),
254           characteristic.getInstanceId(),
255           ByteString.EMPTY,
256           AttStatusCode.UNKNOWN_ERROR
257         )
258       )
259     if (mGatt.readCharacteristic(characteristic)) {
260       waitForValuesReadEnd()
261     }
262     // This method read only one characteristic.
263     return mGattInstanceValuesRead.get(0)
264   }
265 
readCharacteristicUuidBlockingnull266   public suspend fun readCharacteristicUuidBlocking(
267     uuid: UUID,
268     startHandle: Int,
269     endHandle: Int
270   ): ArrayList<GattInstanceValueRead> {
271     mGattInstanceValuesRead = arrayListOf()
272     // Init mGattInstanceValuesRead with characteristics values.
273     for (service: BluetoothGattService in mGatt.services.orEmpty()) {
274       for (characteristic: BluetoothGattCharacteristic in service.characteristics) {
275         if (
276           characteristic.getUuid() == uuid &&
277             characteristic.getInstanceId() >= startHandle &&
278             characteristic.getInstanceId() <= endHandle
279         ) {
280           mGattInstanceValuesRead.add(
281             GattInstanceValueRead(
282               uuid,
283               characteristic.getInstanceId(),
284               ByteString.EMPTY,
285               AttStatusCode.UNKNOWN_ERROR
286             )
287           )
288           check(
289             mGatt.readUsingCharacteristicUuid(
290               uuid,
291               characteristic.getInstanceId(),
292               characteristic.getInstanceId()
293             )
294           )
295           waitForValuesRead()
296         }
297       }
298     }
299     // All needed characteristics are read.
300     mValuesRead.value = 0
301 
302     // When PTS tests with wrong UUID, we return an empty GattInstanceValueRead
303     // with UNKNOWN_ERROR so the MMI can confirm the fail. We also have to try
304     // and read the characteristic anyway for the PTS to validate the test.
305     if (mGattInstanceValuesRead.size == 0) {
306       mGattInstanceValuesRead.add(
307         GattInstanceValueRead(uuid, startHandle, ByteString.EMPTY, AttStatusCode.UNKNOWN_ERROR)
308       )
309       mGatt.readUsingCharacteristicUuid(uuid, startHandle, endHandle)
310     }
311     return mGattInstanceValuesRead
312   }
313 
readDescriptorBlockingnull314   public suspend fun readDescriptorBlocking(
315     descriptor: BluetoothGattDescriptor
316   ): GattInstanceValueRead {
317     // Init mGattInstanceValuesRead with descriptor values.
318     mGattInstanceValuesRead =
319       arrayListOf(
320         GattInstanceValueRead(
321           descriptor.getUuid(),
322           descriptor.getInstanceId(),
323           ByteString.EMPTY,
324           AttStatusCode.UNKNOWN_ERROR
325         )
326       )
327     if (mGatt.readDescriptor(descriptor)) {
328       waitForValuesReadEnd()
329     }
330     // This method read only one descriptor.
331     return mGattInstanceValuesRead.get(0)
332   }
333 
writeCharacteristicBlockingnull334   public suspend fun writeCharacteristicBlocking(
335     characteristic: BluetoothGattCharacteristic,
336     value: ByteArray
337   ): GattInstanceValueWrote {
338     GattInstanceValueWrote(
339       characteristic.getUuid(),
340       characteristic.getInstanceId(),
341       AttStatusCode.UNKNOWN_ERROR
342     )
343     if (
344       mGatt.writeCharacteristic(
345         characteristic,
346         value,
347         BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
348       ) == BluetoothStatusCodes.SUCCESS
349     ) {
350       waitForWriteEnd()
351     }
352     return mGattInstanceValueWrote
353   }
354 
writeDescriptorBlockingnull355   public suspend fun writeDescriptorBlocking(
356     descriptor: BluetoothGattDescriptor,
357     value: ByteArray
358   ): GattInstanceValueWrote {
359     GattInstanceValueWrote(
360       descriptor.getUuid(),
361       descriptor.getInstanceId(),
362       AttStatusCode.UNKNOWN_ERROR
363     )
364     if (mGatt.writeDescriptor(descriptor, value) == BluetoothStatusCodes.SUCCESS) {
365       waitForWriteEnd()
366     }
367     return mGattInstanceValueWrote
368   }
369 
disconnectInstancenull370   public fun disconnectInstance() {
371     require(isConnected()) { "Trying to disconnect an already disconnected device $mDevice" }
372     mGatt.disconnect()
373     gattInstances.remove(mDevice.address)
374   }
375 
toStringnull376   override fun toString(): String {
377     return "GattInstance($mDevice)"
378   }
379 }
380