Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b95cb1f9 authored by Etienne Ruffieux's avatar Etienne Ruffieux
Browse files

Multiple fixes for PTS-bot GATT profile tests

- Read characteristics using UUID now returns a list of
read characteristics. Each characteristics that has a
matching UUID and handle contained in the start handle
and end handle of the request is read.
- MMIs don't return "No" anymore, instead either we
assert that the result is correct or raise an exception if
the check does not pass
- Ran the ktfmt on Gatt.kt and GattInstance.kt
- Fixed SDP tests that checked the wrong value

Bug: 239103399
Test: atest pts-bot:GATT/CL/GAR --rerun-until-failure
Test: atest pts-bot:GATT/CL/GAD --rerun-until-failure
Tag: #feature
Ignore-AOSP-First: cherry-pick
Merged-In: Ife8d43545c77c2eb52d0a0ca57643a6213c0103c
Change-Id: Ife8d43545c77c2eb52d0a0ca57643a6213c0103c
parent e8ed5442
Loading
Loading
Loading
Loading
+88 −51
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ from mmi2grpc._proxy import ProfileProxy
from pandora.gatt_grpc import GATT
from pandora.host_grpc import Host
from pandora.gatt_pb2 import AttStatusCode
from pandora.gatt_pb2 import ReadCharacteristicResponse
from pandora.gatt_pb2 import ReadCharacteristicsFromUuidResponse

# Tests that need GATT cache cleared before discovering services.
NEEDS_CACHE_CLEARED = {
@@ -39,7 +41,7 @@ class GATTProxy(ProfileProxy):
        self.services = None
        self.characteristics = None
        self.descriptors = None
        self.read_value = None
        self.read_response = None

    @assert_description
    def MMI_IUT_INITIATE_CONNECTION(self, test, pts_addr: bytes, **kwargs):
@@ -72,7 +74,7 @@ class GATTProxy(ProfileProxy):
        self.services = None
        self.characteristics = None
        self.descriptors = None
        self.read_value = None
        self.read_response = None
        return "OK"

    @assert_description
@@ -266,9 +268,8 @@ class GATTProxy(ProfileProxy):
                        stringHandleToInt(all_matches[i + 1]),\
                        formatUuid(all_matches[i + 3])):
                    found_services += 1
        if found_services == (len(all_matches) / 4):
        assert found_services == (len(all_matches) / 4)
        return "Yes"
        return "No"

    def MMI_IUT_DISCOVER_SERVICE_UUID(self, description: str, **kwargs):
        """
@@ -344,7 +345,7 @@ class GATTProxy(ProfileProxy):
            if characteristic.handle == stringHandleToInt(all_matches[0])\
                    and characteristic.uuid == formatUuid(all_matches[1]):
                return "Yes"
        return "No"
        raise ValueError

    @assert_description
    def MMI_CONFIRM_NO_CHARACTERISTICSUUID_SMALL(self, **kwargs):
@@ -394,7 +395,7 @@ class GATTProxy(ProfileProxy):
        for descriptor in self.descriptors:
            if descriptor.handle == handle and descriptor.uuid == uuid:
                return "Yes"
        return "No"
        raise ValueError

    def MMI_IUT_DISCOVER_ALL_SERVICE_RECORD(self, pts_addr: bytes, description: str, **kwargs):
        """
@@ -404,11 +405,10 @@ class GATTProxy(ProfileProxy):
        discover basic rate all primary services.
        """

        uuid = formatUuid(re.findall("'([a0-Z9]*)'O", description))
        uuid = formatSdpUuid(re.findall("'([a0-Z9]*)'O", description)[0])
        self.services = self.gatt.DiscoverServicesSdp(address=pts_addr).service_uuids
        if uuid in self.services:
        assert uuid in self.services
        return "Yes"
        return "No"

    def MMI_IUT_SEND_READ_CHARACTERISTIC_HANDLE(self, description: str, **kwargs):
        """
@@ -419,7 +419,7 @@ class GATTProxy(ProfileProxy):

        assert self.connection is not None
        handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0])
        self.read_value = self.gatt.ReadCharacteristicFromHandle(\
        self.read_response = self.gatt.ReadCharacteristicFromHandle(\
                connection=self.connection, handle=handle)
        return "OK"

@@ -434,10 +434,14 @@ class GATTProxy(ProfileProxy):
        a characteristic.
        """

        assert self.read_value is not None
        if self.read_value.status == AttStatusCode.INVALID_HANDLE:
        if type(self.read_response) is ReadCharacteristicResponse:
            assert self.read_response.readValue is not None
            assert self.read_response.readValue.status == AttStatusCode.INVALID_HANDLE
        elif type(self.read_response) is ReadCharacteristicsFromUuidResponse:
            assert self.read_response.readValues is not None
            assert AttStatusCode.INVALID_HANDLE in\
                    list(map(lambda read_value: read_value.status, self.read_response.readValues))
        return "Yes"
        return "No"

    @assert_description
    def MMI_IUT_CONFIRM_READ_NOT_PERMITTED(self, **kwargs):
@@ -450,13 +454,19 @@ class GATTProxy(ProfileProxy):
        when read a characteristic.
        """

        assert self.read_value is not None
        # Android read error doesn't return an error code so we have to also
        # compare to the generic error code here.
        if self.read_value.status == AttStatusCode.READ_NOT_PERMITTED or\
                self.read_value.status == AttStatusCode.UNKNOWN_ERROR:
        if type(self.read_response) is ReadCharacteristicResponse:
            assert self.read_response.readValue is not None
            assert self.read_response.readValue.status == AttStatusCode.READ_NOT_PERMITTED or\
                    self.read_response.readValue.status == AttStatusCode.UNKNOWN_ERROR
        elif type(self.read_response) is ReadCharacteristicsFromUuidResponse:
            assert self.read_response.readValues is not None
            assert AttStatusCode.READ_NOT_PERMITTED in\
                    list(map(lambda read_value: read_value.status, self.read_response.readValues)) or\
                    AttStatusCode.UNKNOWN_ERROR in\
                    list(map(lambda read_value: read_value.status, self.read_response.readValues))
        return "Yes"
        return "No"

    @assert_description
    def MMI_IUT_CONFIRM_READ_AUTHENTICATION(self, **kwargs):
@@ -469,10 +479,14 @@ class GATTProxy(ProfileProxy):
        a characteristic.
        """

        assert self.read_value is not None
        if self.read_value.status == AttStatusCode.INSUFFICIENT_AUTHENTICATION:
        if type(self.read_response) is ReadCharacteristicResponse:
            assert self.read_response.readValue is not None
            assert self.read_response.readValue.status == AttStatusCode.INSUFFICIENT_AUTHENTICATION
        elif type(self.read_response) is ReadCharacteristicsFromUuidResponse:
            assert self.read_response.readValues is not None
            assert AttStatusCode.INSUFFICIENT_AUTHENTICATION in\
                    list(map(lambda read_value: read_value.status, self.read_response.readValues))
        return "Yes"
        return "No"

    def MMI_IUT_SEND_READ_CHARACTERISTIC_UUID(self, description: str, **kwargs):
        """
@@ -485,7 +499,7 @@ class GATTProxy(ProfileProxy):

        assert self.connection is not None
        matches = re.findall("'([a0-Z9]*)'O", description)
        self.read_value = self.gatt.ReadCharacteristicFromUuid(\
        self.read_response = self.gatt.ReadCharacteristicsFromUuid(\
                connection=self.connection, uuid=formatUuid(matches[0]),\
                start_handle=stringHandleToInt(matches[1]),\
                end_handle=stringHandleToInt(matches[2]))
@@ -502,13 +516,19 @@ class GATTProxy(ProfileProxy):
        read a characteristic.
        """

        assert self.read_value is not None
        # Android read error doesn't return an error code so we have to also
        # compare to the generic error code here.
        if self.read_value.status == AttStatusCode.ATTRIBUTE_NOT_FOUND or\
                self.read_value.status == AttStatusCode.UNKNOWN_ERROR:
        if type(self.read_response) is ReadCharacteristicResponse:
            assert self.read_response.readValue is not None
            assert self.read_response.readValue.status == AttStatusCode.ATTRIBUTE_NOT_FOUND or\
                    self.read_response.readValue.status == AttStatusCode.UNKNOWN_ERROR
        elif type(self.read_response) is ReadCharacteristicsFromUuidResponse:
            assert self.read_response.readValues is not None
            assert AttStatusCode.ATTRIBUTE_NOT_FOUND in\
                    list(map(lambda read_value: read_value.status, self.read_response.readValues)) or\
                    AttStatusCode.UNKNOWN_ERROR in\
                    list(map(lambda read_value: read_value.status, self.read_response.readValues))
        return "Yes"
        return "No"

    def MMI_IUT_SEND_READ_GREATER_OFFSET(self, description: str, **kwargs):
        """
@@ -523,7 +543,7 @@ class GATTProxy(ProfileProxy):
        # Unfortunately for testing, this will always work.
        assert self.connection is not None
        handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0])
        self.read_value = self.gatt.ReadCharacteristicFromHandle(\
        self.read_response = self.gatt.ReadCharacteristicFromHandle(\
                connection=self.connection, handle=handle)
        return "OK"

@@ -551,10 +571,14 @@ class GATTProxy(ProfileProxy):
        Under Test (IUT) indicate Application error when read a characteristic.
        """

        assert self.read_value is not None
        if self.read_value.status == AttStatusCode.APPLICATION_ERROR:
        if type(self.read_response) is ReadCharacteristicResponse:
            assert self.read_response.readValue is not None
            assert self.read_response.readValue.status == AttStatusCode.APPLICATION_ERROR
        elif type(self.read_response) is ReadCharacteristicsFromUuidResponse:
            assert self.read_response.readValues is not None
            assert AttStatusCode.APPLICATION_ERROR in\
                    list(map(lambda read_value: read_value.status, self.read_response.readValues))
        return "Yes"
        return "No"

    def MMI_IUT_CONFIRM_READ_CHARACTERISTIC_VALUE(self, description: str, **kwargs):
        """
@@ -566,11 +590,14 @@ class GATTProxy(ProfileProxy):
        send Read characteristic to PTS random select adopted database.
        """

        assert self.read_value is not None
        characteristic_value = bytes.fromhex(re.findall("'([a0-Z9]*)'O", description)[0])
        if characteristic_value[0] in self.read_value.value:
        if type(self.read_response) is ReadCharacteristicResponse:
            assert self.read_response.readValue is not None
            assert characteristic_value in self.read_response.readValue.value
        elif type(self.read_response) is ReadCharacteristicsFromUuidResponse:
            assert self.read_response.readValues is not None
            assert characteristic_value in list(map(lambda read_value: read_value.value, self.read_response.readValues))
        return "Yes"
        return "No"

    def MMI_IUT_READ_BY_TYPE_UUID(self, description: str, **kwargs):
        """
@@ -581,7 +608,7 @@ class GATTProxy(ProfileProxy):

        assert self.connection is not None
        matches = re.findall("'([a0-Z9]*)'O", description)
        self.read_value = self.gatt.ReadCharacteristicFromUuid(\
        self.read_response = self.gatt.ReadCharacteristicsFromUuid(\
                connection=self.connection, uuid=formatUuid(matches[0]),\
                start_handle=0x0001,\
                end_handle=0xffff)
@@ -599,7 +626,7 @@ class GATTProxy(ProfileProxy):

        assert self.connection is not None
        uuid = formatUuid(re.findall("'([a0-Z9-]*)'O", description)[0])
        self.read_value = self.gatt.ReadCharacteristicFromUuid(\
        self.read_response = self.gatt.ReadCharacteristicsFromUuid(\
                connection=self.connection, uuid=uuid, start_handle=0x0001, end_handle=0xffff)
        return "OK"

@@ -614,11 +641,15 @@ class GATTProxy(ProfileProxy):
        can send Read long characteristic to PTS random select adopted database.
        """

        assert self.read_value is not None
        bytes_value = bytes.fromhex(re.search("value='(.*)'O", description)[1])
        if self.read_value.value == bytes_value:
        if type(self.read_response) is ReadCharacteristicResponse:
            assert self.read_response.readValue is not None
            assert self.read_response.readValue.value == bytes_value
        elif type(self.read_response) is ReadCharacteristicsFromUuidResponse:
            assert self.read_response.readValues is not None
            assert bytes_value in\
                    list(map(lambda read_value: read_value.value, self.read_response.readValues))
        return "Yes"
        return "No"

    def MMI_IUT_SEND_READ_DESCIPTOR_HANDLE(self, description: str, **kwargs):
        """
@@ -629,7 +660,7 @@ class GATTProxy(ProfileProxy):

        assert self.connection is not None
        handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0])
        self.read_value = self.gatt.ReadCharacteristicDescriptorFromHandle(\
        self.read_response = self.gatt.ReadCharacteristicDescriptorFromHandle(\
                connection=self.connection, handle=handle)
        return "OK"

@@ -643,11 +674,10 @@ class GATTProxy(ProfileProxy):
        send Read Descriptor to PTS random select adopted database.
        """

        assert self.read_value is not None
        assert self.read_response.readValue is not None
        bytes_value = bytes.fromhex(re.search("value='(.*)'O", description)[1])
        if self.read_value.value == bytes_value:
        assert self.read_response.readValue.value == bytes_value
        return "Yes"
        return "No"


common_uuid = "0000XXXX-0000-1000-8000-00805f9b34fb"
@@ -685,6 +715,13 @@ def formatUuid(uuid: str):
        return uuid


# PTS asks wrong uuid for services discovered by SDP in some tests
def formatSdpUuid(uuid: str):
    if uuid[3] == '1':
        uuid = uuid[:3] + 'f'
    return common_uuid.replace(common_uuid[4:8], uuid.lower())


def compareIncludedServices(service, service_handle, included_handle, included_uuid):
    """
    Compares included services with given values.
+17 −8
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ service GATT {
  rpc ReadCharacteristicFromHandle(ReadCharacteristicRequest) returns (ReadCharacteristicResponse);

  // Reads characteristic with given uuid, start and end handles.
  rpc ReadCharacteristicFromUuid(ReadCharacteristicFromUuidRequest) returns (ReadCharacteristicResponse);
  rpc ReadCharacteristicsFromUuid(ReadCharacteristicsFromUuidRequest) returns (ReadCharacteristicsFromUuidResponse);

  // Reads characteristic with given descriptor handle.
  rpc ReadCharacteristicDescriptorFromHandle(ReadCharacteristicDescriptorRequest) returns (ReadCharacteristicDescriptorResponse);
@@ -72,6 +72,12 @@ message GattDescriptor {
  string uuid = 3;
}

message GattReadValue {
  uint32 handle = 1;
  bytes value = 2;
  AttStatusCode status = 3;
}

// Request for the `ExchangeMTU` rpc.
message ExchangeMTURequest {
  Connection connection = 1;
@@ -122,18 +128,22 @@ message ReadCharacteristicRequest {
  uint32 handle = 2;
}

// Request for the `ReadCharacteristicFromUuid` rpc.
message ReadCharacteristicFromUuidRequest {
// Request for the `ReadCharacteristicsFromUuid` rpc.
message ReadCharacteristicsFromUuidRequest {
  Connection connection = 1;
  string uuid = 2;
  uint32 start_handle = 3;
  uint32 end_handle = 4;
}

// Response for the `ReadCharacteristicFromHandle` and `ReadCharacteristicFromUuid` rpc.
// Response for the `ReadCharacteristicFromHandle` rpc.
message ReadCharacteristicResponse {
  bytes value = 1;
  AttStatusCode status = 2;
  GattReadValue readValue = 1;
}

// Response for the `ReadCharacteristicsFromUuid` rpc.
message ReadCharacteristicsFromUuidResponse {
  repeated GattReadValue readValues = 1;
}

// Request for the `ReadCharacteristicDescriptorFromHandle` rpc.
@@ -144,6 +154,5 @@ message ReadCharacteristicDescriptorRequest {

// Response for the `ReadCharacteristicDescriptorFromHandle` rpc.
message ReadCharacteristicDescriptorResponse {
  bytes value = 1;
  AttStatusCode status = 2;
  GattReadValue readValue = 1;
}
+120 −86

File changed.

Preview size limit exceeded, changes collapsed.

+159 −99
Original line number Diff line number Diff line
@@ -21,23 +21,17 @@ import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothProfile
import android.content.Context
import android.util.Log

import com.google.protobuf.ByteString

import java.util.UUID

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withTimeoutOrNull
import pandora.GattProto.*

/**
 * GattInstance extends and simplifies Android GATT APIs without re-implementing them.
 */
/** GattInstance extends and simplifies Android GATT APIs without re-implementing them. */
@kotlinx.coroutines.ExperimentalCoroutinesApi
class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mContext: Context) {
  private val TAG = "GattInstance"
@@ -45,39 +39,42 @@ class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mConte

  private var mServiceDiscovered = MutableStateFlow(false)
  private var mConnectionState = MutableStateFlow(BluetoothProfile.STATE_DISCONNECTED)
  private var mValueRead = MutableStateFlow(false)
  private var mValuesRead = MutableStateFlow(0)

  /**
   * Wrapper for characteristic and descriptor reading.
   * Uuid, startHandle and endHandle are used to compare with the callback returned object.
   * Value and status can be read once the read has been done.
   * Wrapper for characteristic and descriptor reading. Uuid, startHandle and endHandle are used to
   * compare with the callback returned object. Value and status can be read once the read has been
   * done. ByteString and AttStatusCode are used to ensure compatibility with proto.
   */
  class GattInstanceValueRead(var uuid: UUID?, var startHandle: Int, var endHandle: Int,
      var value: ByteArray?, var status: Int) {}
  private var mGattInstanceValueRead = GattInstanceValueRead(
      null, 0, 0, byteArrayOf(), BluetoothGatt.GATT_FAILURE)
  class GattInstanceValueRead(
    var uuid: UUID?,
    var handle: Int,
    var value: ByteString?,
    var status: AttStatusCode
  ) {}
  private var mGattInstanceValuesRead = arrayListOf<GattInstanceValueRead>()

  companion object GattManager {
    val gattInstances: MutableMap<String, GattInstance> = mutableMapOf<String, GattInstance>()
    fun get(address: String): GattInstance {
      val instance = gattInstances.get(address)
      requireNotNull(instance) {
        "Unable to find GATT instance for $address"
      }
      requireNotNull(instance) { "Unable to find GATT instance for $address" }
      return instance
    }
    fun get(address: ByteString): GattInstance {
      val instance = gattInstances.get(address.toByteArray().decodeToString())
      requireNotNull(instance) {
        "Unable to find GATT instance for $address"
      }
      requireNotNull(instance) { "Unable to find GATT instance for $address" }
      return instance
    }
  }

  private val mCallback = object : BluetoothGattCallback() {
    override fun onConnectionStateChange(bluetoothGatt: BluetoothGatt,
        status: Int, newState: Int) {
  private val mCallback =
    object : BluetoothGattCallback() {
      override fun onConnectionStateChange(
        bluetoothGatt: BluetoothGatt,
        status: Int,
        newState: Int
      ) {
        Log.i(TAG, "$mDevice connection state changed to $newState")
        mConnectionState.value = newState
        if (newState == BluetoothProfile.STATE_DISCONNECTED) {
@@ -92,36 +89,48 @@ class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mConte
        }
      }

    override fun onCharacteristicRead(bluetoothGatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic, value: ByteArray, status: Int) {
      override fun onCharacteristicRead(
        bluetoothGatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic,
        value: ByteArray,
        status: Int
      ) {
        Log.i(TAG, "onCharacteristicRead, status: $status")
      if (characteristic.getUuid() == mGattInstanceValueRead.uuid
          && characteristic.getInstanceId() >= mGattInstanceValueRead.startHandle
          && characteristic.getInstanceId() <= mGattInstanceValueRead.endHandle) {
        mGattInstanceValueRead.value = value
        mGattInstanceValueRead.status = status
        mValueRead.value = true
        for (gattInstanceValueRead: GattInstanceValueRead in mGattInstanceValuesRead) {
          if (
            characteristic.getUuid() == gattInstanceValueRead.uuid &&
              characteristic.getInstanceId() == gattInstanceValueRead.handle
          ) {
            gattInstanceValueRead.value = ByteString.copyFrom(value)
            gattInstanceValueRead.status = AttStatusCode.forNumber(status)
            mValuesRead.value++
          }
        }
      }

    override fun onDescriptorRead(bluetoothGatt: BluetoothGatt,
        descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
      override fun onDescriptorRead(
        bluetoothGatt: BluetoothGatt,
        descriptor: BluetoothGattDescriptor,
        status: Int,
        value: ByteArray
      ) {
        Log.i(TAG, "onDescriptorRead, status: $status")
      if (descriptor.getUuid() == mGattInstanceValueRead.uuid
          && descriptor.getInstanceId() >= mGattInstanceValueRead.startHandle
          && descriptor.getInstanceId() <= mGattInstanceValueRead.endHandle) {
        mGattInstanceValueRead.value = value
        mGattInstanceValueRead.status = status
        mValueRead.value = true
        for (gattInstanceValueRead: GattInstanceValueRead in mGattInstanceValuesRead) {
          if (
            descriptor.getUuid() == gattInstanceValueRead.uuid &&
              descriptor.getInstanceId() >= gattInstanceValueRead.handle
          ) {
            gattInstanceValueRead.value = ByteString.copyFrom(value)
            gattInstanceValueRead.status = AttStatusCode.forNumber(status)
            mValuesRead.value++
          }
        }
      }
    }

  init {
    if (!isBLETransport()) {
      require(isBonded()) {
        "Trying to connect non BLE GATT on a not bonded device $mDevice"
      }
      require(isBonded()) { "Trying to connect non BLE GATT on a not bonded device $mDevice" }
    }
    require(gattInstances.get(mDevice.address) == null) {
      "Trying to connect GATT on an already connected device $mDevice"
@@ -129,9 +138,7 @@ class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mConte

    mGatt = mDevice.connectGatt(mContext, false, mCallback, mTransport)

    checkNotNull(mGatt) {
      "Failed to connect GATT on $mDevice"
    }
    checkNotNull(mGatt) { "Failed to connect GATT on $mDevice" }
    gattInstances.put(mDevice.address, this)
  }

@@ -167,56 +174,109 @@ class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mConte
    }
  }

  public suspend fun waitForValueReadEnd() {
    if (mValueRead.value != true) {
      mValueRead.first { it == true }
  public suspend fun waitForValuesReadEnd() {
    if (mValuesRead.value < mGattInstanceValuesRead.size) {
      mValuesRead.first { it == mGattInstanceValuesRead.size }
    }
    mValuesRead.value = 0
  }

  public suspend fun waitForValuesRead() {
    if (mValuesRead.value < mGattInstanceValuesRead.size) {
      mValuesRead.first { it == mGattInstanceValuesRead.size }
    }
    mValueRead.value = false
  }

  public suspend fun readCharacteristicBlocking(
      characteristic: BluetoothGattCharacteristic): GattInstanceValueRead {
    // Init mGattInstanceValueRead with characteristic values.
    mGattInstanceValueRead = GattInstanceValueRead(
        characteristic.getUuid(), characteristic.getInstanceId(), characteristic.getInstanceId(),
        byteArrayOf(), BluetoothGatt.GATT_FAILURE)
    characteristic: BluetoothGattCharacteristic
  ): GattInstanceValueRead {
    // Init mGattInstanceValuesRead with characteristic values.
    mGattInstanceValuesRead =
      arrayListOf(
        GattInstanceValueRead(
          characteristic.getUuid(),
          characteristic.getInstanceId(),
          ByteString.EMPTY,
          AttStatusCode.UNKNOWN_ERROR
        )
      )
    if (mGatt.readCharacteristic(characteristic)) {
      waitForValueReadEnd()
      waitForValuesReadEnd()
    }
    return mGattInstanceValueRead
    // This method read only one characteristic.
    return mGattInstanceValuesRead.get(0)
  }

  public suspend fun readCharacteristicUuidBlocking(
      uuid: UUID, startHandle: Int, endHandle: Int): GattInstanceValueRead {
    // Init mGattInstanceValueRead with characteristic values.
    mGattInstanceValueRead = GattInstanceValueRead(
        uuid, startHandle, endHandle, byteArrayOf(), BluetoothGatt.GATT_FAILURE)
    if (mGatt.readUsingCharacteristicUuid(uuid, startHandle, endHandle)){
      // We have to timeout here as one test will try to read on an inexistant
      // characteristic. We don't discover services when reading by uuid so we
      // can't check if the characteristic exists beforehand. PTS is also waiting
      // for the read to happen so we have to read anyway.
      withTimeoutOrNull(1000L) { waitForValueReadEnd() }
    uuid: UUID,
    startHandle: Int,
    endHandle: Int
  ): ArrayList<GattInstanceValueRead> {
    mGattInstanceValuesRead = arrayListOf()
    // Init mGattInstanceValuesRead with characteristics values.
    for (service: BluetoothGattService in mGatt.services.orEmpty()) {
      for (characteristic: BluetoothGattCharacteristic in service.characteristics) {
        if (
          characteristic.getUuid() == uuid &&
            characteristic.getInstanceId() >= startHandle &&
            characteristic.getInstanceId() <= endHandle
        ) {
          mGattInstanceValuesRead.add(
            GattInstanceValueRead(
              uuid,
              characteristic.getInstanceId(),
              ByteString.EMPTY,
              AttStatusCode.UNKNOWN_ERROR
            )
          )
          check(
            mGatt.readUsingCharacteristicUuid(
              uuid,
              characteristic.getInstanceId(),
              characteristic.getInstanceId()
            )
          )
          waitForValuesRead()
        }
      }
    }
    // All needed characteristics are read.
    mValuesRead.value = 0

    // When PTS tests with wrong UUID, we return an empty GattInstanceValueRead
    // with UNKNOWN_ERROR so the MMI can confirm the fail. We also have to try
    // and read the characteristic anyway for the PTS to validate the test.
    if (mGattInstanceValuesRead.size == 0) {
      mGattInstanceValuesRead.add(
        GattInstanceValueRead(uuid, startHandle, ByteString.EMPTY, AttStatusCode.UNKNOWN_ERROR)
      )
      mGatt.readUsingCharacteristicUuid(uuid, startHandle, endHandle)
    }
    return mGattInstanceValueRead
    return mGattInstanceValuesRead
  }

  public suspend fun readDescriptorBlocking(
      descriptor: BluetoothGattDescriptor): GattInstanceValueRead {
    // Init mGattInstanceValueRead with descriptor values.
    mGattInstanceValueRead = GattInstanceValueRead(
        descriptor.getUuid(), descriptor.getInstanceId(), descriptor.getInstanceId(),
        byteArrayOf(), BluetoothGatt.GATT_FAILURE)
    descriptor: BluetoothGattDescriptor
  ): GattInstanceValueRead {
    // Init mGattInstanceValuesRead with descriptor values.
    mGattInstanceValuesRead =
      arrayListOf(
        GattInstanceValueRead(
          descriptor.getUuid(),
          descriptor.getInstanceId(),
          ByteString.EMPTY,
          AttStatusCode.UNKNOWN_ERROR
        )
      )
    if (mGatt.readDescriptor(descriptor)) {
      waitForValueReadEnd()
      waitForValuesReadEnd()
    }
    return mGattInstanceValueRead
    // This method read only one descriptor.
    return mGattInstanceValuesRead.get(0)
  }

  public fun disconnectInstance() {
    require(isConnected()) {
      "Trying to disconnect an already disconnected device $mDevice"
    }
    require(isConnected()) { "Trying to disconnect an already disconnected device $mDevice" }
    mGatt.disconnect()
    gattInstances.remove(mDevice.address)
  }