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

Commit a6e05b3a authored by Omair Kamil's avatar Omair Kamil Committed by Gerrit Code Review
Browse files

Merge "Add GATT client notification and indication tests." into main

parents f6b3d626 54e4d21f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ android_test_helper_app {
    ],

    static_libs: [
        "TestParameterInjector",
        "androidx.core_core",
        "androidx.test.espresso.intents",
        "androidx.test.ext.junit",
+109 −12
Original line number Diff line number Diff line
@@ -34,13 +34,17 @@ import android.content.Context;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Log;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import com.android.bluetooth.flags.Flags;
import com.android.compatibility.common.util.AdoptShellPermissionsRule;

import com.google.protobuf.ByteString;
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;

import org.junit.Assume;
import org.junit.ClassRule;
import org.junit.Ignore;
@@ -50,28 +54,36 @@ import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.invocation.Invocation;

import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.UUID;

import pandora.GattProto.AttStatusCode;
import pandora.GattProto.GattCharacteristicParams;
import pandora.GattProto.GattServiceParams;
import pandora.GattProto.IndicateOnCharacteristicRequest;
import pandora.GattProto.IndicateOnCharacteristicResponse;
import pandora.GattProto.NotifyOnCharacteristicRequest;
import pandora.GattProto.NotifyOnCharacteristicResponse;
import pandora.GattProto.RegisterServiceRequest;
import pandora.HostProto.AdvertiseRequest;
import pandora.HostProto.AdvertiseResponse;
import pandora.HostProto.OwnAddressType;

@RunWith(AndroidJUnit4.class)
@RunWith(TestParameterInjector.class)
public class GattClientTest {
    private static final String TAG = "GattClientTest";
    private static final int ANDROID_MTU = 517;
    private static final int MTU_REQUESTED = 23;
    private static final int ANOTHER_MTU_REQUESTED = 42;
    private static final String NOTIFICATION_VALUE = "hello world";

    private static final UUID GAP_UUID = UUID.fromString("00001800-0000-1000-8000-00805f9b34fb");
    private static final UUID CCCD_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");

    private static final UUID WRITABLE_SERVICE_UUID =
    private static final UUID TEST_SERVICE_UUID =
            UUID.fromString("00000000-0000-0000-0000-00000000000");
    private static final UUID WRITABLE_CHARACTERISTIC_UUID =
    private static final UUID TEST_CHARACTERISTIC_UUID =
            UUID.fromString("00010001-0000-0000-0000-000000000000");
    @ClassRule public static final AdoptShellPermissionsRule PERM = new AdoptShellPermissionsRule();

@@ -206,8 +218,7 @@ public class GattClientTest {
                    .onServicesDiscovered(any(), eq(BluetoothGatt.GATT_SUCCESS));

            BluetoothGattCharacteristic characteristic =
                    gatt.getService(WRITABLE_SERVICE_UUID)
                            .getCharacteristic(WRITABLE_CHARACTERISTIC_UUID);
                    gatt.getService(TEST_SERVICE_UUID).getCharacteristic(TEST_CHARACTERISTIC_UUID);

            byte[] newValue = new byte[] {13};

@@ -223,6 +234,51 @@ public class GattClientTest {
        }
    }

    @Test
    public void clientGattNotifyOrIndicateCharacteristic(@TestParameter boolean isIndicate)
            throws Exception {
        registerNotificationIndicationGattService(isIndicate);

        BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
        BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback);

        try {
            gatt.discoverServices();
            verify(gattCallback, timeout(10000))
                    .onServicesDiscovered(any(), eq(BluetoothGatt.GATT_SUCCESS));

            BluetoothGattCharacteristic characteristic =
                    gatt.getService(TEST_SERVICE_UUID).getCharacteristic(TEST_CHARACTERISTIC_UUID);

            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CCCD_UUID);
            descriptor.setValue(
                    isIndicate
                            ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
                            : BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            assertThat(gatt.writeDescriptor(descriptor)).isTrue();

            verify(gattCallback, timeout(5000))
                    .onDescriptorWrite(any(), eq(descriptor), eq(BluetoothGatt.GATT_SUCCESS));

            gatt.setCharacteristicNotification(characteristic, true);

            if (isIndicate) {
                Log.i(TAG, "Triggering characteristic indication");
                triggerCharacteristicIndication(characteristic.getInstanceId());
            } else {
                Log.i(TAG, "Triggering characteristic notification");
                triggerCharacteristicNotification(characteristic.getInstanceId());
            }

            verify(gattCallback, timeout(5000))
                    .onCharacteristicChanged(
                            any(), any(), eq(NOTIFICATION_VALUE.getBytes(StandardCharsets.UTF_8)));

        } finally {
            disconnectAndWaitDisconnection(gatt, gattCallback);
        }
    }

    @Test
    @RequiresFlagsEnabled(Flags.FLAG_ENUMERATE_GATT_ERRORS)
    public void connectTimeout() {
@@ -263,12 +319,10 @@ public class GattClientTest {
                    .onServicesDiscovered(any(), eq(BluetoothGatt.GATT_SUCCESS));

            BluetoothGattCharacteristic characteristic =
                    gatt.getService(WRITABLE_SERVICE_UUID)
                            .getCharacteristic(WRITABLE_CHARACTERISTIC_UUID);
                    gatt.getService(TEST_SERVICE_UUID).getCharacteristic(TEST_CHARACTERISTIC_UUID);

            BluetoothGattCharacteristic characteristic2 =
                    gatt2.getService(WRITABLE_SERVICE_UUID)
                            .getCharacteristic(WRITABLE_CHARACTERISTIC_UUID);
                    gatt2.getService(TEST_SERVICE_UUID).getCharacteristic(TEST_CHARACTERISTIC_UUID);

            byte[] newValue = new byte[] {13};

@@ -310,13 +364,13 @@ public class GattClientTest {
        GattCharacteristicParams characteristicParams =
                GattCharacteristicParams.newBuilder()
                        .setProperties(BluetoothGattCharacteristic.PROPERTY_WRITE)
                        .setUuid(WRITABLE_CHARACTERISTIC_UUID.toString())
                        .setUuid(TEST_CHARACTERISTIC_UUID.toString())
                        .build();

        GattServiceParams serviceParams =
                GattServiceParams.newBuilder()
                        .addCharacteristics(characteristicParams)
                        .setUuid(WRITABLE_SERVICE_UUID.toString())
                        .setUuid(TEST_SERVICE_UUID.toString())
                        .build();

        RegisterServiceRequest request =
@@ -325,6 +379,49 @@ public class GattClientTest {
        mBumble.gattBlocking().registerService(request);
    }

    private void registerNotificationIndicationGattService(boolean isIndicate) {
        GattCharacteristicParams characteristicParams =
                GattCharacteristicParams.newBuilder()
                        .setProperties(
                                isIndicate
                                        ? BluetoothGattCharacteristic.PROPERTY_INDICATE
                                        : BluetoothGattCharacteristic.PROPERTY_NOTIFY)
                        .setUuid(TEST_CHARACTERISTIC_UUID.toString())
                        .build();

        GattServiceParams serviceParams =
                GattServiceParams.newBuilder()
                        .addCharacteristics(characteristicParams)
                        .setUuid(TEST_SERVICE_UUID.toString())
                        .build();

        RegisterServiceRequest request =
                RegisterServiceRequest.newBuilder().setService(serviceParams).build();

        mBumble.gattBlocking().registerService(request);
    }

    private void triggerCharacteristicNotification(int instanceId) {
        NotifyOnCharacteristicRequest req =
                NotifyOnCharacteristicRequest.newBuilder()
                        .setHandle(instanceId)
                        .setValue(ByteString.copyFromUtf8(NOTIFICATION_VALUE))
                        .build();
        NotifyOnCharacteristicResponse resp = mBumble.gattBlocking().notifyOnCharacteristic(req);
        assertThat(resp.getStatus()).isEqualTo(AttStatusCode.SUCCESS);
    }

    private void triggerCharacteristicIndication(int instanceId) {
        IndicateOnCharacteristicRequest req =
                IndicateOnCharacteristicRequest.newBuilder()
                        .setHandle(instanceId)
                        .setValue(ByteString.copyFromUtf8(NOTIFICATION_VALUE))
                        .build();
        IndicateOnCharacteristicResponse resp =
                mBumble.gattBlocking().indicateOnCharacteristic(req);
        assertThat(resp.getStatus()).isEqualTo(AttStatusCode.SUCCESS);
    }

    private void advertiseWithBumble() {
        AdvertiseRequest request =
                AdvertiseRequest.newBuilder()
+24 −0
Original line number Diff line number Diff line
@@ -43,6 +43,12 @@ service GATT {

  // Wait for characteristic notification/indication
  rpc WaitCharacteristicNotification(WaitCharacteristicNotificationRequest) returns (WaitCharacteristicNotificationResponse);

  // Notify on characteristic
  rpc NotifyOnCharacteristic(NotifyOnCharacteristicRequest) returns (NotifyOnCharacteristicResponse);

  // Indicate on characteristic
  rpc IndicateOnCharacteristic(IndicateOnCharacteristicRequest) returns (IndicateOnCharacteristicResponse);
}

enum AttStatusCode {
@@ -252,3 +258,21 @@ message RegisterServiceRequest {
message RegisterServiceResponse {
  GattService service = 1;
}

message NotifyOnCharacteristicRequest {
  uint32 handle = 1;
  bytes value = 2;
}

message NotifyOnCharacteristicResponse {
  AttStatusCode status = 1;
}

message IndicateOnCharacteristicRequest {
  uint32 handle = 1;
  bytes value = 2;
}

message IndicateOnCharacteristicResponse {
  AttStatusCode status = 1;
}
+27 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ from bumble.gatt_client import CharacteristicProxy, ServiceProxy
from bumble.pandora import utils
from pandora_experimental.gatt_grpc_aio import GATTServicer
from pandora_experimental.gatt_pb2 import (
    ATTRIBUTE_NOT_FOUND,
    SUCCESS,
    AttStatusCode,
    AttValue,
@@ -37,6 +38,10 @@ from pandora_experimental.gatt_pb2 import (
    GattCharacteristic,
    GattCharacteristicDescriptor,
    GattService,
    IndicateOnCharacteristicRequest,
    IndicateOnCharacteristicResponse,
    NotifyOnCharacteristicRequest,
    NotifyOnCharacteristicResponse,
    ReadCharacteristicDescriptorRequest,
    ReadCharacteristicDescriptorResponse,
    ReadCharacteristicRequest,
@@ -283,3 +288,25 @@ class GATTService(GATTServicer):

        logging.info(f"RegisterService complete")
        return RegisterServiceResponse()

    @utils.rpc
    async def NotifyOnCharacteristic(self, request: NotifyOnCharacteristicRequest,
                                     context: grpc.ServicerContext) -> NotifyOnCharacteristicResponse:
        logging.info(f"NotifyOnCharacteristic")

        attr = self.device.gatt_server.get_attribute(request.handle)
        if not attr:
            return NotifyOnCharacteristicResponse(status=ATTRIBUTE_NOT_FOUND)
        await self.device.notify_subscribers(attr, request.value)
        return NotifyOnCharacteristicResponse(status=SUCCESS)

    @utils.rpc
    async def IndicateOnCharacteristic(self, request: IndicateOnCharacteristicRequest,
                                       context: grpc.ServicerContext) -> IndicateOnCharacteristicResponse:
        logging.info(f"IndicateOnCharacteristic")

        attr = self.device.gatt_server.get_attribute(request.handle)
        if not attr:
            return IndicateOnCharacteristicResponse(status=ATTRIBUTE_NOT_FOUND)
        await self.device.indicate_subscribers(attr, request.value)
        return IndicateOnCharacteristicResponse(status=SUCCESS)