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

Commit cf66648a authored by Kyunglyul Hyun's avatar Kyunglyul Hyun
Browse files

Fix BluetoothGatt busy state after failure

Previously, when BluetoothGatt#writeCharacteristics
failed early, the `mDeviceBusy` incorrectly remain
`true`. This prevented subsequent operations
on the BluetoothGatt instance.

This change ensures the `mDeviceBusy` is properly set to
`false` after a failure is returned by the service,
allowing for continued use of the instance.

It also adds a test for ensuring the behavior.

Bug: 322580271
Bug: 322509603
Test: atest GattClientTest

Change-Id: I6aea853745e15aca5c4caf75037e680592e74c81
parent b98008c9
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeoutException;


/**
 * Public API for the Bluetooth GATT Profile.
 *
@@ -1686,6 +1687,13 @@ public final class BluetoothGatt implements BluetoothProfile {
            }
            throw e.rethrowAsRuntimeException();
        }
        if (Flags.gattFixDeviceBusy()) {
            if (requestStatus != BluetoothStatusCodes.SUCCESS) {
                synchronized (mDeviceBusyLock) {
                    mDeviceBusy = false;
                }
            }
        }

        return requestStatus;
    }
@@ -1837,6 +1845,11 @@ public final class BluetoothGatt implements BluetoothProfile {
            }
            throw e.rethrowAsRuntimeException();
        }
        if (Flags.gattFixDeviceBusy()) {
            synchronized (mDeviceBusyLock) {
                mDeviceBusy = false;
            }
        }
        return BluetoothStatusCodes.ERROR_UNKNOWN;
    }

+75 −19
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;

@@ -40,6 +41,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.bluetooth.flags.Flags;
import com.android.compatibility.common.util.AdoptShellPermissionsRule;

import org.junit.Assume;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Rule;
@@ -67,6 +69,10 @@ public class GattClientTest {

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

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

    @Rule public final PandoraDevice mBumble = new PandoraDevice();
@@ -202,20 +208,9 @@ public class GattClientTest {
            verify(gattCallback, timeout(10000))
                    .onServicesDiscovered(any(), eq(BluetoothGatt.GATT_SUCCESS));

            BluetoothGattCharacteristic characteristic = null;

            outer:
            for (BluetoothGattService candidateService : gatt.getServices()) {
                for (BluetoothGattCharacteristic candidateCharacteristic :
                        candidateService.getCharacteristics()) {
                    if ((candidateCharacteristic.getProperties()
                                    & BluetoothGattCharacteristic.PROPERTY_WRITE)
                            != 0) {
                        characteristic = candidateCharacteristic;
                        break outer;
                    }
                }
            }
            BluetoothGattCharacteristic characteristic =
                    gatt.getService(WRITABLE_SERVICE_UUID)
                            .getCharacteristic(WRITABLE_CHARACTERISTIC_UUID);

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

@@ -249,21 +244,82 @@ public class GattClientTest {
                        eq(BluetoothProfile.STATE_DISCONNECTED));
    }

    private void registerWritableGattService() {
    @RequiresFlagsEnabled(Flags.FLAG_GATT_FIX_DEVICE_BUSY)
    @Test
    public void consecutiveWriteCharacteristicFails_thenSuccess() throws Exception {
        Assume.assumeTrue(Flags.gattFixDeviceBusy());

        registerWritableGattService();

        BluetoothGattCallback gattCallback = mock(BluetoothGattCallback.class);
        BluetoothGattCallback gattCallback2 = mock(BluetoothGattCallback.class);

        BluetoothGatt gatt = connectGattAndWaitConnection(gattCallback);
        BluetoothGatt gatt2 = connectGattAndWaitConnection(gattCallback2);

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

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

        String characteristicUuidString = "11111111-1111-1111-1111-111111111111";
        String serviceUuidString = "00000000-0000-0000-0000-000000000000";
            BluetoothGattCharacteristic characteristic2 =
                    gatt2.getService(WRITABLE_SERVICE_UUID)
                            .getCharacteristic(WRITABLE_CHARACTERISTIC_UUID);

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

            gatt.writeCharacteristic(
                    characteristic, newValue, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);

            // TODO: b/324355496 - Make the test consistent when Bumble supports holding a response.
            // Skip the test if the second write succeeded.
            Assume.assumeFalse(
                    gatt2.writeCharacteristic(
                                    characteristic2,
                                    newValue,
                                    BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
                            == BluetoothStatusCodes.SUCCESS);

            verify(gattCallback, timeout(5000))
                    .onCharacteristicWrite(
                            any(), eq(characteristic), eq(BluetoothGatt.GATT_SUCCESS));
            verify(gattCallback2, never())
                    .onCharacteristicWrite(
                            any(), eq(characteristic), eq(BluetoothGatt.GATT_SUCCESS));

            assertThat(
                            gatt2.writeCharacteristic(
                                    characteristic2,
                                    newValue,
                                    BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT))
                    .isEqualTo(BluetoothStatusCodes.SUCCESS);
            verify(gattCallback2, timeout(5000))
                    .onCharacteristicWrite(
                            any(), eq(characteristic2), eq(BluetoothGatt.GATT_SUCCESS));
        } finally {
            disconnectAndWaitDisconnection(gatt, gattCallback);
            disconnectAndWaitDisconnection(gatt2, gattCallback2);
        }
    }

    private void registerWritableGattService() {
        GattCharacteristicParams characteristicParams =
                GattCharacteristicParams.newBuilder()
                        .setProperties(BluetoothGattCharacteristic.PROPERTY_WRITE)
                        .setUuid(characteristicUuidString)
                        .setUuid(WRITABLE_CHARACTERISTIC_UUID.toString())
                        .build();

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

        RegisterServiceRequest request =