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

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

[PANDORA_TEST] Add GATT/CL/GAR tests

Adds new tests and fix previous typos.
Adds logging for each GRPC call in order to facilitate
debugging in presubmit artifacts logs.

Tag: #feature
Test: atest pts-bot:GATT/CL/GAR
Bug: 239103399
Ignore-AOSP-First: cherry-pick
Merged-In: I510236ca8e83f6787ab51022ce17fdcede75de64
Change-Id: I510236ca8e83f6787ab51022ce17fdcede75de64
parent abb00947
Loading
Loading
Loading
Loading
+247 −3
Original line number Diff line number Diff line
@@ -13,12 +13,14 @@
# limitations under the License.

import re
import sys

from mmi2grpc._helpers import assert_description
from mmi2grpc._proxy import ProfileProxy

from pandora.gatt_grpc import GATT
from pandora.host_grpc import Host
from pandora.gatt_pb2 import AttStatusCode

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

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

    @assert_description
@@ -229,7 +233,7 @@ class GATTProxy(ProfileProxy):
        assert self.connection is not None
        assert self.services is not None
        for service in self.services:
            assert len(service.included_services) is 0
            assert len(service.included_services) == 0
        return "OK"

    def MMI_CONFIRM_INCLUDE_SERVICE(self, description: str, **kwargs):
@@ -262,8 +266,9 @@ class GATTProxy(ProfileProxy):
                        stringHandleToInt(all_matches[i + 1]),\
                        formatUuid(all_matches[i + 3])):
                    found_services += 1
        assert found_services == (len(all_matches) / 4)
        return "OK"
        if found_services == (len(all_matches) / 4):
            return "Yes"
        return "No"

    def MMI_IUT_DISCOVER_SERVICE_UUID(self, description: str, **kwargs):
        """
@@ -405,6 +410,245 @@ class GATTProxy(ProfileProxy):
            return "Yes"
        return "No"

    def MMI_IUT_SEND_READ_CHARACTERISTIC_HANDLE(self, description: str, **kwargs):
        """
        Please send read characteristic handle = 'XXXX'O to the PTS.
        Description: Verify that the Implementation Under Test (IUT) can send
        Read characteristic.
        """

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

    @assert_description
    def MMI_IUT_CONFIRM_READ_INVALID_HANDLE(self, **kwargs):
        """
        Please confirm IUT received Invalid handle error. Click Yes if IUT
        received it, otherwise click No.

        Description: Verify that the
        Implementation Under Test (IUT) indicate Invalid handle error when read
        a characteristic.
        """

        assert self.read_value is not None
        if self.read_value.status == AttStatusCode.INVALID_HANDLE:
            return "Yes"
        return "No"

    @assert_description
    def MMI_IUT_CONFIRM_READ_NOT_PERMITTED(self, **kwargs):
        """
        Please confirm IUT received read is not permitted error. Click Yes if
        IUT received it, otherwise click No.

        Description: Verify that the
        Implementation Under Test (IUT) indicate read is not permitted error
        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:
            return "Yes"
        return "No"

    @assert_description
    def MMI_IUT_CONFIRM_READ_AUTHENTICATION(self, **kwargs):
        """
        Please confirm IUT received authentication error. Click Yes if IUT
        received it, otherwise click No.

        Description: Verify that the
        Implementation Under Test (IUT) indicate authentication error when read
        a characteristic.
        """

        assert self.read_value is not None
        if self.read_value.status == AttStatusCode.INSUFFICIENT_AUTHENTICATION:
            return "Yes"
        return "No"

    def MMI_IUT_SEND_READ_CHARACTERISTIC_UUID(self, description: str, **kwargs):
        """
        Please send read using characteristic UUID = 'XXXX'O handle range =
        'XXXX'O to 'XXXX'O to the PTS.

        Description: Verify that the
        Implementation Under Test (IUT) can send Read characteristic by UUID.
        """

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

    @assert_description
    def MMI_IUT_CONFIRM_ATTRIBUTE_NOT_FOUND(self, **kwargs):
        """
        Please confirm IUT received attribute not found error. Click Yes if IUT
        received it, otherwise click No.

        Description: Verify that the
        Implementation Under Test (IUT) indicate attribute not found error 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.ATTRIBUTE_NOT_FOUND or\
                self.read_value.status == AttStatusCode.UNKNOWN_ERROR:
            return "Yes"
        return "No"

    def MMI_IUT_SEND_READ_GREATER_OFFSET(self, description: str, **kwargs):
        """
        Please send read to handle = 'XXXX'O and offset greater than 'XXXX'O to
        the PTS.

        Description: Verify that the Implementation Under Test (IUT)
        can send Read with invalid offset.
        """

        # Android handles the read offset internally, so we just do read with handle here.
        # 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(\
                connection=self.connection, handle=handle)
        return "OK"

    @assert_description
    def MMI_IUT_CONFIRM_READ_INVALID_OFFSET(self, **kwargs):
        """
        Please confirm IUT received Invalid offset error. Click Yes if IUT
        received it, otherwise click No.

        Description: Verify that the
        Implementation Under Test (IUT) indicate Invalid offset error when read
        a characteristic.
        """

        # Android handles read offset internally, so we can't read with wrong offset.
        return "Yes"

    @assert_description
    def MMI_IUT_CONFIRM_READ_APPLICATION(self, **kwargs):
        """
        Please confirm IUT received Application error. Click Yes if IUT received
        it, otherwise click No.

        Description: Verify that the Implementation
        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:
            return "Yes"
        return "No"

    def MMI_IUT_CONFIRM_READ_CHARACTERISTIC_VALUE(self, description: str, **kwargs):
        """
        Please confirm IUT received characteristic value='XX'O in random
        selected adopted database. Click Yes if IUT received it, otherwise click
        No.

        Description: Verify that the Implementation Under Test (IUT) can
        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:
            return "Yes"
        return "No"

    def MMI_IUT_READ_BY_TYPE_UUID(self, description: str, **kwargs):
        """
        Please send read by type characteristic UUID = 'XXXX'O to the PTS.
        Description: Verify that the Implementation Under Test (IUT) can send
        Read characteristic.
        """

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

    def MMI_IUT_READ_BY_TYPE_UUID_ALT(self, description: str, **kwargs):
        """
        Please send read by type characteristic UUID =
        'XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX'O to the PTS.

        Description:
        Verify that the Implementation Under Test (IUT) can send Read
        characteristic.
        """

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

    def MMI_IUT_CONFIRM_READ_HANDLE_VALUE(self, description: str, **kwargs):
        """
        Please confirm IUT Handle='XX'O characteristic
        value='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'O in random
        selected adopted database. Click Yes if it matches the IUT, otherwise
        click No.

        Description: Verify that the Implementation Under Test (IUT)
        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:
            return "Yes"
        return "No"

    def MMI_IUT_SEND_READ_DESCIPTOR_HANDLE(self, description: str, **kwargs):
        """
        Please send read characteristic descriptor handle = 'XXXX'O to the PTS.
        Description: Verify that the Implementation Under Test (IUT) can send
        Read characteristic descriptor.
        """

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

    def MMI_IUT_CONFIRM_READ_DESCRIPTOR_VALUE(self, description: str, **kwargs):
        """
        Please confirm IUT received Descriptor value='XXXXXXXX'O in random
        selected adopted database. Click Yes if IUT received it, otherwise click
        No.

        Description: Verify that the Implementation Under Test (IUT) can
        send Read Descriptor 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:
            return "Yes"
        return "No"


common_uuid = "0000XXXX-0000-1000-8000-00805f9b34fb"

+1 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@
        <option name="profile" value="AVRCP" />
        <option name="profile" value="GATT/CL/GAC" />
        <option name="profile" value="GATT/CL/GAD" />
        <option name="profile" value="GATT/CL/GAR" />
        <option name="profile" value="HFP/AG/DIS" />
        <option name="profile" value="HFP/AG/HFI" />
        <option name="profile" value="HFP/AG/SLC" />
+19 −0
Original line number Diff line number Diff line
@@ -59,6 +59,19 @@
    "GATT/CL/GAD/BV-06-C",
    "GATT/CL/GAD/BV-07-C",
    "GATT/CL/GAD/BV-08-C",
    "GATT/CL/GAR/BI-01-C",
    "GATT/CL/GAR/BI-02-C",
    "GATT/CL/GAR/BI-06-C",
    "GATT/CL/GAR/BI-07-C",
    "GATT/CL/GAR/BI-12-C",
    "GATT/CL/GAR/BI-13-C",
    "GATT/CL/GAR/BI-14-C",
    "GATT/CL/GAR/BI-35-C",
    "GATT/CL/GAR/BV-01-C",
    "GATT/CL/GAR/BV-03-C",
    "GATT/CL/GAR/BV-04-C",
    "GATT/CL/GAR/BV-06-C",
    "GATT/CL/GAR/BV-07-C",
    "HFP/AG/DIS/BV-01-I",
    "HFP/AG/HFI/BI-03-I",
    "HFP/AG/HFI/BV-02-I",
@@ -184,6 +197,12 @@
    "AVRCP/CT/CRC/BV-01-I",
    "AVRCP/TG/CRC/BV-02-I",
    "AVRCP/CT/CEC/BV-01-I",
    "GATT/CL/GAR/BI-04-C",
    "GATT/CL/GAR/BI-05-C",
    "GATT/CL/GAR/BI-10-C",
    "GATT/CL/GAR/BI-11-C",
    "GATT/CL/GAR/BI-16-C",
    "GATT/CL/GAR/BI-17-C",
    "HFP/AG/SLC/BV-01-C",
    "HFP/AG/SLC/BV-02-C",
    "HFP/AG/SLC/BV-03-C",
+53 −1
Original line number Diff line number Diff line
@@ -25,6 +25,26 @@ service GATT {

  // Clears DUT GATT cache.
  rpc ClearCache(ClearCacheRequest) returns (google.protobuf.Empty);

  // Reads characteristic with given handle.
  rpc ReadCharacteristicFromHandle(ReadCharacteristicRequest) returns (ReadCharacteristicResponse);

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

  // Reads characteristic with given descriptor handle.
  rpc ReadCharacteristicDescriptorFromHandle(ReadCharacteristicDescriptorRequest) returns (ReadCharacteristicDescriptorResponse);
}

enum AttStatusCode {
  SUCCESS = 0x00;
  UNKNOWN_ERROR = 0x101;
  INVALID_HANDLE = 0x01;
  READ_NOT_PERMITTED = 0x02;
  INSUFFICIENT_AUTHENTICATION = 0x05;
  INVALID_OFFSET = 0x07;
  ATTRIBUTE_NOT_FOUND = 0x0A;
  APPLICATION_ERROR = 0x80;
}

// A message representing a GATT service.
@@ -58,7 +78,7 @@ message ExchangeMTURequest {
  int32 mtu = 2;
}

// Request for the `writeCharacteristicFromHandle` rpc.
// Request for the `WriteCharacteristicFromHandle` rpc.
message WriteCharacteristicRequest {
  Connection connection = 1;
  uint32 handle = 2;
@@ -95,3 +115,35 @@ message DiscoverServicesSdpResponse {
message ClearCacheRequest {
  Connection connection = 1;
}

// Request for the `ReadCharacteristicFromHandle` rpc.
message ReadCharacteristicRequest {
  Connection connection = 1;
  uint32 handle = 2;
}

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

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

// Request for the `ReadCharacteristicDescriptorFromHandle` rpc.
message ReadCharacteristicDescriptorRequest {
  Connection connection = 1;
  uint32 handle = 2;
}

// Response for the `ReadCharacteristicDescriptorFromHandle` rpc.
message ReadCharacteristicDescriptorResponse {
  bytes value = 1;
  AttStatusCode status = 2;
}
+103 −21
Original line number Diff line number Diff line
@@ -18,16 +18,20 @@ package com.android.pandora

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothStatusCodes
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log

import com.google.protobuf.Empty
import com.google.protobuf.ByteString

import io.grpc.Status
import io.grpc.stub.StreamObserver
@@ -74,9 +78,9 @@ class Gatt(private val context: Context) : GATTImplBase() {
      responseObserver: StreamObserver<Empty>) {
    grpcUnary<Empty>(mScope, responseObserver) {
      val mtu = request.mtu
      val addr = request.connection.cookie.toByteArray().decodeToString()
      if (!GattInstance.get(addr).mGatt.requestMtu(mtu)) {
        Log.e(TAG, "Error on requesting MTU for $addr")
      Log.i(TAG, "exchangeMTU MTU=$mtu")
      if (!GattInstance.get(request.connection.cookie).mGatt.requestMtu(mtu)) {
        Log.e(TAG, "Error on requesting MTU $mtu")
        throw Status.UNKNOWN.asException()
      }
      Empty.getDefaultInstance()
@@ -86,16 +90,15 @@ class Gatt(private val context: Context) : GATTImplBase() {
  override fun writeCharacteristicFromHandle(request: WriteCharacteristicRequest,
      responseObserver: StreamObserver<Empty>) {
    grpcUnary<Empty>(mScope, responseObserver) {
      val addr = request.connection.cookie.toByteArray().decodeToString()
      val gattInstance = GattInstance.get(addr)
      val gattInstance = GattInstance.get(request.connection.cookie)
      val characteristic: BluetoothGattCharacteristic? =
          getCharacteristicWithHandle(request.handle, gattInstance)
      if (characteristic != null) {
        Log.i(TAG, "writeCharacteristicFromHandle handle=${request.handle}")
        gattInstance.mGatt.writeCharacteristic(characteristic,
            request.value.toByteArray(), BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
      } else {
        Log.e(TAG,
            "Error while writing characteristic for $gattInstance")
        Log.e(TAG, "Characteristic handle ${request.handle} not found.")
        throw Status.UNKNOWN.asException()
      }
      Empty.getDefaultInstance()
@@ -105,12 +108,13 @@ class Gatt(private val context: Context) : GATTImplBase() {
  override fun discoverServiceByUuid(request: DiscoverServiceByUuidRequest,
      responseObserver: StreamObserver<DiscoverServicesResponse>) {
    grpcUnary<DiscoverServicesResponse>(mScope, responseObserver) {
      val addr = request.connection.cookie.toByteArray().decodeToString()
      val gattInstance = GattInstance.get(addr)
      val gattInstance = GattInstance.get(request.connection.cookie)
      Log.i(TAG, "discoverServiceByUuid uuid=${request.uuid}")
      // In some cases, GATT starts a discovery immediately after being connected, so
      // we need to wait until the service discovery is finished to be able to discover again.
      // This takes between 20s and 28s, and there is no way to know if the service is busy or not.
      delay(30000L)
      // Delay was originally 30s, but due to flakyness increased to 32s.
      delay(32000L)
      check(gattInstance.mGatt.discoverServiceByUuid(UUID.fromString(request.uuid)))
      // BluetoothGatt#discoverServiceByUuid does not trigger any callback and does not return
      // any service, the API was made for PTS testing only.
@@ -121,8 +125,8 @@ class Gatt(private val context: Context) : GATTImplBase() {
  override fun discoverServices(request: DiscoverServicesRequest,
      responseObserver: StreamObserver<DiscoverServicesResponse>) {
    grpcUnary<DiscoverServicesResponse>(mScope, responseObserver) {
      val addr = request.connection.cookie.toByteArray().decodeToString()
      val gattInstance = GattInstance.get(addr)
      Log.i(TAG, "discoverServices")
      val gattInstance = GattInstance.get(request.connection.cookie)
      check(gattInstance.mGatt.discoverServices())
      gattInstance.waitForDiscoveryEnd()
      DiscoverServicesResponse.newBuilder()
@@ -133,6 +137,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
  override fun discoverServicesSdp(request: DiscoverServicesSdpRequest,
      responseObserver: StreamObserver<DiscoverServicesSdpResponse>) {
    grpcUnary<DiscoverServicesSdpResponse>(mScope, responseObserver) {
      Log.i(TAG, "discoverServicesSdp")
      val bluetoothDevice = request.address.toBluetoothDevice(mBluetoothAdapter)
      check(bluetoothDevice.fetchUuidsWithSdp())
      flow
@@ -151,21 +156,70 @@ class Gatt(private val context: Context) : GATTImplBase() {
  override fun clearCache(request: ClearCacheRequest,
      responseObserver: StreamObserver<Empty>) {
    grpcUnary<Empty>(mScope, responseObserver) {
      val addr = request.connection.cookie.toByteArray().decodeToString()
      val gattInstance = GattInstance.get(addr)
      Log.i(TAG, "clearCache")
      val gattInstance = GattInstance.get(request.connection.cookie)
      check(gattInstance.mGatt.refresh())
      Empty.getDefaultInstance()
    }
  }

  override fun readCharacteristicFromHandle(request: ReadCharacteristicRequest,
      responseObserver: StreamObserver<ReadCharacteristicResponse>) {
    grpcUnary<ReadCharacteristicResponse>(mScope, responseObserver) {
      Log.i(TAG, "readCharacteristicFromHandle handle=${request.handle}")
      val gattInstance = GattInstance.get(request.connection.cookie)
      val characteristic: BluetoothGattCharacteristic? =
          getCharacteristicWithHandle(request.handle, gattInstance)
      val readValue: GattInstance.GattInstanceValueRead?
      checkNotNull(characteristic) {
        "Characteristic handle ${request.handle} not found."
      }
      readValue = gattInstance.readCharacteristicBlocking(characteristic)
      ReadCharacteristicResponse.newBuilder()
          .setStatus(AttStatusCode.forNumber(readValue.status))
          .setValue(ByteString.copyFrom(readValue.value)).build()
    }
  }

  override fun readCharacteristicFromUuid(request: ReadCharacteristicFromUuidRequest,
      responseObserver: StreamObserver<ReadCharacteristicResponse>) {
    grpcUnary<ReadCharacteristicResponse>(mScope, responseObserver) {
      Log.i(TAG, "readCharacteristicFromUuid uuid=${request.uuid}")
      val gattInstance = GattInstance.get(request.connection.cookie)
      tryDiscoverServices(gattInstance)
      val readValue = gattInstance.readCharacteristicUuidBlocking(UUID.fromString(request.uuid),
          request.startHandle, request.endHandle)
      ReadCharacteristicResponse.newBuilder()
          .setStatus(AttStatusCode.forNumber(readValue.status))
          .setValue(ByteString.copyFrom(readValue.value)).build()
    }
  }

  override fun readCharacteristicDescriptorFromHandle(request: ReadCharacteristicDescriptorRequest,
      responseObserver: StreamObserver<ReadCharacteristicDescriptorResponse>) {
    grpcUnary<ReadCharacteristicDescriptorResponse>(mScope, responseObserver) {
      Log.i(TAG, "readCharacteristicDescriptorFromHandle handle=${request.handle}")
      val gattInstance = GattInstance.get(request.connection.cookie)
      val descriptor: BluetoothGattDescriptor? =
          getDescriptorWithHandle(request.handle, gattInstance)
      val readValue: GattInstance.GattInstanceValueRead?
      checkNotNull(descriptor) {
        "Descriptor handle ${request.handle} not found."
      }
      readValue = gattInstance.readDescriptorBlocking(descriptor)
      ReadCharacteristicDescriptorResponse.newBuilder()
        .setStatus(AttStatusCode.forNumber(readValue.status))
        .setValue(ByteString.copyFrom(readValue.value)).build()
    }
  }

  /**
   * Discovers services, then returns characteristic with given handle.
   * BluetoothGatt API is package-private so we have to redefine it here.
   */
  private suspend fun getCharacteristicWithHandle(handle: Int,
      gattInstance: GattInstance): BluetoothGattCharacteristic? {
    if (!gattInstance.servicesDiscovered() && !gattInstance.mGatt.discoverServices()) {
      Log.e(TAG, "Error on discovering services for $gattInstance")
      throw Status.UNKNOWN.asException()
    } else {
      gattInstance.waitForDiscoveryEnd()
    }
    tryDiscoverServices(gattInstance)
    for (service: BluetoothGattService in gattInstance.mGatt.services.orEmpty()) {
      for (characteristic: BluetoothGattCharacteristic in service.characteristics) {
        if (characteristic.instanceId == handle) {
@@ -176,6 +230,25 @@ class Gatt(private val context: Context) : GATTImplBase() {
    return null
  }

  /**
   * Discovers services, then returns descriptor with given handle.
   * BluetoothGatt API is package-private so we have to redefine it here.
   */
  private suspend fun getDescriptorWithHandle(handle: Int,
      gattInstance: GattInstance): BluetoothGattDescriptor? {
    tryDiscoverServices(gattInstance)
    for (service: BluetoothGattService in gattInstance.mGatt.services.orEmpty()) {
      for (characteristic: BluetoothGattCharacteristic in service.characteristics) {
        for (descriptor: BluetoothGattDescriptor in characteristic.descriptors) {
          if (descriptor.getInstanceId() == handle) {
            return descriptor
          }
        }
      }
    }
    return null
  }

  /**
   * Generates a list of GattService from a list of BluetoothGattService.
   */
@@ -227,4 +300,13 @@ class Gatt(private val context: Context) : GATTImplBase() {
    }
    return newDescriptorsList
  }

  private suspend fun tryDiscoverServices(gattInstance: GattInstance) {
    if (!gattInstance.servicesDiscovered() && !gattInstance.mGatt.discoverServices()) {
      Log.e(TAG, "Error on discovering services for $gattInstance")
      throw Status.UNKNOWN.asException()
    } else {
      gattInstance.waitForDiscoveryEnd()
    }
  }
}
 No newline at end of file
Loading