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> = 47 mutableMapOf() 48 49 /** 50 * Wrapper for characteristic and descriptor reading. Uuid, startHandle and endHandle are used 51 * to compare with the callback returned object. Value and status can be read once the read has 52 * been done. ByteString and AttStatusCode are used to ensure compatibility with proto. 53 */ 54 class GattInstanceValueRead( 55 var uuid: UUID?, 56 var handle: Int, 57 var value: ByteString?, 58 var status: AttStatusCode 59 ) {} 60 private var mGattInstanceValuesRead = arrayListOf<GattInstanceValueRead>() 61 62 class GattInstanceValueWrote(var uuid: UUID?, var handle: Int, var status: AttStatusCode) {} 63 private var mGattInstanceValueWrote = 64 GattInstanceValueWrote(null, 0, AttStatusCode.UNKNOWN_ERROR) 65 66 companion object GattManager { 67 val gattInstances: MutableMap<String, GattInstance> = mutableMapOf<String, GattInstance>() getnull68 fun get(address: String): GattInstance { 69 val instance = gattInstances.get(address) 70 requireNotNull(instance) { "Unable to find GATT instance for $address" } 71 return instance 72 } getnull73 fun get(address: ByteString): GattInstance { 74 val instance = gattInstances.get(address.toByteArray().decodeToString()) 75 requireNotNull(instance) { "Unable to find GATT instance for $address" } 76 return instance 77 } clearAllInstancesnull78 fun clearAllInstances() { 79 gattInstances.clear() 80 } 81 } 82 83 private val mCallback = 84 object : BluetoothGattCallback() { onConnectionStateChangenull85 override fun onConnectionStateChange( 86 bluetoothGatt: BluetoothGatt, 87 status: Int, 88 newState: Int 89 ) { 90 Log.i(TAG, "$mDevice connection state changed to $newState") 91 mConnectionState.value = newState 92 if (newState == BluetoothProfile.STATE_DISCONNECTED) { 93 gattInstances.remove(mDevice.address) 94 } 95 } 96 onServicesDiscoverednull97 override fun onServicesDiscovered(bluetoothGatt: BluetoothGatt, status: Int) { 98 if (status == BluetoothGatt.GATT_SUCCESS) { 99 Log.i(TAG, "Services have been discovered for $mDevice") 100 mServiceDiscovered.value = true 101 } 102 } 103 onCharacteristicReadnull104 override fun onCharacteristicRead( 105 bluetoothGatt: BluetoothGatt, 106 characteristic: BluetoothGattCharacteristic, 107 value: ByteArray, 108 status: Int 109 ) { 110 Log.i(TAG, "onCharacteristicRead, status: $status") 111 for (gattInstanceValueRead: GattInstanceValueRead in mGattInstanceValuesRead) { 112 if ( 113 characteristic.getUuid() == gattInstanceValueRead.uuid && 114 characteristic.getInstanceId() == gattInstanceValueRead.handle 115 ) { 116 gattInstanceValueRead.value = ByteString.copyFrom(value) 117 gattInstanceValueRead.status = AttStatusCode.forNumber(status) 118 mValuesRead.value++ 119 } 120 } 121 } 122 onDescriptorReadnull123 override fun onDescriptorRead( 124 bluetoothGatt: BluetoothGatt, 125 descriptor: BluetoothGattDescriptor, 126 status: Int, 127 value: ByteArray 128 ) { 129 Log.i(TAG, "onDescriptorRead, status: $status") 130 for (gattInstanceValueRead: GattInstanceValueRead in mGattInstanceValuesRead) { 131 if ( 132 descriptor.getUuid() == gattInstanceValueRead.uuid && 133 descriptor.getInstanceId() >= gattInstanceValueRead.handle 134 ) { 135 gattInstanceValueRead.value = ByteString.copyFrom(value) 136 gattInstanceValueRead.status = AttStatusCode.forNumber(status) 137 mValuesRead.value++ 138 } 139 } 140 } 141 onCharacteristicWritenull142 override fun onCharacteristicWrite( 143 bluetoothGatt: BluetoothGatt, 144 characteristic: BluetoothGattCharacteristic, 145 status: Int 146 ) { 147 Log.i(TAG, "onCharacteristicWrite, status: $status") 148 mGattInstanceValueWrote.status = AttStatusCode.forNumber(status) 149 mValueWrote.value = true 150 } 151 onDescriptorWritenull152 override fun onDescriptorWrite( 153 bluetoothGatt: BluetoothGatt, 154 descriptor: BluetoothGattDescriptor, 155 status: Int 156 ) { 157 Log.i(TAG, "onDescriptorWrite, status: $status") 158 mGattInstanceValueWrote.status = AttStatusCode.forNumber(status) 159 mValueWrote.value = true 160 } 161 onCharacteristicChangednull162 override fun onCharacteristicChanged( 163 bluetoothGatt: BluetoothGatt, 164 characteristic: BluetoothGattCharacteristic, 165 value: ByteArray 166 ) { 167 Log.i( 168 TAG, 169 "onCharacteristicChanged, characteristic: " + 170 characteristic.getUuid().toString().uppercase() 171 ) 172 mCharacteristicChangedMap[characteristic] = true 173 mOnCharacteristicChanged.value = true 174 } 175 } 176 177 init { 178 if (!isBLETransport()) { <lambda>null179 require(isBonded()) { "Trying to connect non BLE GATT on a not bonded device $mDevice" } 180 } <lambda>null181 require(gattInstances.get(mDevice.address) == null) { 182 "Trying to connect GATT on an already connected device $mDevice" 183 } 184 185 mGatt = mDevice.connectGatt(mContext, false, mCallback, mTransport) 186 <lambda>null187 checkNotNull(mGatt) { "Failed to connect GATT on $mDevice" } 188 gattInstances.put(mDevice.address, this) 189 } 190 isConnectednull191 public fun isConnected(): Boolean { 192 return mConnectionState.value == BluetoothProfile.STATE_CONNECTED 193 } 194 isDisconnectednull195 public fun isDisconnected(): Boolean { 196 return mConnectionState.value == BluetoothProfile.STATE_DISCONNECTED 197 } 198 isBondednull199 public fun isBonded(): Boolean { 200 return mDevice.getBondState() == BluetoothDevice.BOND_BONDED 201 } 202 isBLETransportnull203 public fun isBLETransport(): Boolean { 204 return mTransport == BluetoothDevice.TRANSPORT_LE 205 } 206 servicesDiscoverednull207 public fun servicesDiscovered(): Boolean { 208 return mServiceDiscovered.value 209 } 210 waitForOnCharacteristicChangednull211 public suspend fun waitForOnCharacteristicChanged( 212 characteristic: BluetoothGattCharacteristic 213 ): Boolean { 214 if (mOnCharacteristicChanged.value == false) { 215 mOnCharacteristicChanged.first { it == true } 216 } 217 return mCharacteristicChangedMap[characteristic] == true 218 } 219 waitForStatenull220 public suspend fun waitForState(newState: Int) { 221 if (mConnectionState.value != newState) { 222 mConnectionState.first { it == newState } 223 } 224 } 225 waitForDiscoveryEndnull226 public suspend fun waitForDiscoveryEnd() { 227 if (mServiceDiscovered.value != true) { 228 mServiceDiscovered.first { it == true } 229 } 230 } 231 waitForValuesReadEndnull232 public suspend fun waitForValuesReadEnd() { 233 if (mValuesRead.value < mGattInstanceValuesRead.size) { 234 mValuesRead.first { it == mGattInstanceValuesRead.size } 235 } 236 mValuesRead.value = 0 237 } 238 waitForValuesReadnull239 public suspend fun waitForValuesRead() { 240 if (mValuesRead.value < mGattInstanceValuesRead.size) { 241 mValuesRead.first { it == mGattInstanceValuesRead.size } 242 } 243 } 244 waitForWriteEndnull245 public suspend fun waitForWriteEnd() { 246 if (mValueWrote.value != true) { 247 mValueWrote.first { it == true } 248 } 249 mValueWrote.value = false 250 } 251 readCharacteristicBlockingnull252 public suspend fun readCharacteristicBlocking( 253 characteristic: BluetoothGattCharacteristic 254 ): GattInstanceValueRead { 255 // Init mGattInstanceValuesRead with characteristic values. 256 mGattInstanceValuesRead = 257 arrayListOf( 258 GattInstanceValueRead( 259 characteristic.getUuid(), 260 characteristic.getInstanceId(), 261 ByteString.EMPTY, 262 AttStatusCode.UNKNOWN_ERROR 263 ) 264 ) 265 if (mGatt.readCharacteristic(characteristic)) { 266 waitForValuesReadEnd() 267 } 268 // This method read only one characteristic. 269 return mGattInstanceValuesRead.get(0) 270 } 271 readCharacteristicUuidBlockingnull272 public suspend fun readCharacteristicUuidBlocking( 273 uuid: UUID, 274 startHandle: Int, 275 endHandle: Int 276 ): ArrayList<GattInstanceValueRead> { 277 mGattInstanceValuesRead = arrayListOf() 278 // Init mGattInstanceValuesRead with characteristics values. 279 for (service: BluetoothGattService in mGatt.services.orEmpty()) { 280 for (characteristic: BluetoothGattCharacteristic in service.characteristics) { 281 if ( 282 characteristic.getUuid() == uuid && 283 characteristic.getInstanceId() >= startHandle && 284 characteristic.getInstanceId() <= endHandle 285 ) { 286 mGattInstanceValuesRead.add( 287 GattInstanceValueRead( 288 uuid, 289 characteristic.getInstanceId(), 290 ByteString.EMPTY, 291 AttStatusCode.UNKNOWN_ERROR 292 ) 293 ) 294 check( 295 mGatt.readUsingCharacteristicUuid( 296 uuid, 297 characteristic.getInstanceId(), 298 characteristic.getInstanceId() 299 ) 300 ) 301 waitForValuesRead() 302 } 303 } 304 } 305 // All needed characteristics are read. 306 mValuesRead.value = 0 307 308 // When PTS tests with wrong UUID, we return an empty GattInstanceValueRead 309 // with UNKNOWN_ERROR so the MMI can confirm the fail. We also have to try 310 // and read the characteristic anyway for the PTS to validate the test. 311 if (mGattInstanceValuesRead.size == 0) { 312 mGattInstanceValuesRead.add( 313 GattInstanceValueRead( 314 uuid, 315 startHandle, 316 ByteString.EMPTY, 317 AttStatusCode.UNKNOWN_ERROR 318 ) 319 ) 320 mGatt.readUsingCharacteristicUuid(uuid, startHandle, endHandle) 321 } 322 return mGattInstanceValuesRead 323 } 324 readDescriptorBlockingnull325 public suspend fun readDescriptorBlocking( 326 descriptor: BluetoothGattDescriptor 327 ): GattInstanceValueRead { 328 // Init mGattInstanceValuesRead with descriptor values. 329 mGattInstanceValuesRead = 330 arrayListOf( 331 GattInstanceValueRead( 332 descriptor.getUuid(), 333 descriptor.getInstanceId(), 334 ByteString.EMPTY, 335 AttStatusCode.UNKNOWN_ERROR 336 ) 337 ) 338 if (mGatt.readDescriptor(descriptor)) { 339 waitForValuesReadEnd() 340 } 341 // This method read only one descriptor. 342 return mGattInstanceValuesRead.get(0) 343 } 344 writeCharacteristicBlockingnull345 public suspend fun writeCharacteristicBlocking( 346 characteristic: BluetoothGattCharacteristic, 347 value: ByteArray 348 ): GattInstanceValueWrote { 349 GattInstanceValueWrote( 350 characteristic.getUuid(), 351 characteristic.getInstanceId(), 352 AttStatusCode.UNKNOWN_ERROR 353 ) 354 if ( 355 mGatt.writeCharacteristic( 356 characteristic, 357 value, 358 BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT 359 ) == BluetoothStatusCodes.SUCCESS 360 ) { 361 waitForWriteEnd() 362 } 363 return mGattInstanceValueWrote 364 } 365 writeDescriptorBlockingnull366 public suspend fun writeDescriptorBlocking( 367 descriptor: BluetoothGattDescriptor, 368 value: ByteArray 369 ): GattInstanceValueWrote { 370 GattInstanceValueWrote( 371 descriptor.getUuid(), 372 descriptor.getInstanceId(), 373 AttStatusCode.UNKNOWN_ERROR 374 ) 375 if (mGatt.writeDescriptor(descriptor, value) == BluetoothStatusCodes.SUCCESS) { 376 waitForWriteEnd() 377 } 378 return mGattInstanceValueWrote 379 } 380 disconnectInstancenull381 public fun disconnectInstance() { 382 require(isConnected()) { "Trying to disconnect an already disconnected device $mDevice" } 383 mGatt.disconnect() 384 gattInstances.remove(mDevice.address) 385 } 386 toStringnull387 override fun toString(): String { 388 return "GattInstance($mDevice)" 389 } 390 } 391