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