Loading framework/java/android/bluetooth/BluetoothGatt.java +13 −0 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.TimeoutException; /** * Public API for the Bluetooth GATT Profile. * Loading Loading @@ -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; } Loading Loading @@ -1837,6 +1845,11 @@ public final class BluetoothGatt implements BluetoothProfile { } throw e.rethrowAsRuntimeException(); } if (Flags.gattFixDeviceBusy()) { synchronized (mDeviceBusyLock) { mDeviceBusy = false; } } return BluetoothStatusCodes.ERROR_UNKNOWN; } Loading framework/tests/bumble/src/android/bluetooth/GattClientTest.java +75 −19 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -199,20 +205,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}; Loading Loading @@ -246,21 +241,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 = Loading Loading
framework/java/android/bluetooth/BluetoothGatt.java +13 −0 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.TimeoutException; /** * Public API for the Bluetooth GATT Profile. * Loading Loading @@ -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; } Loading Loading @@ -1837,6 +1845,11 @@ public final class BluetoothGatt implements BluetoothProfile { } throw e.rethrowAsRuntimeException(); } if (Flags.gattFixDeviceBusy()) { synchronized (mDeviceBusyLock) { mDeviceBusy = false; } } return BluetoothStatusCodes.ERROR_UNKNOWN; } Loading
framework/tests/bumble/src/android/bluetooth/GattClientTest.java +75 −19 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -199,20 +205,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}; Loading Loading @@ -246,21 +241,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 = Loading