summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TEST_MAPPING6
-rw-r--r--android/app/src/com/android/bluetooth/gatt/GattService.java4
-rw-r--r--android/app/src/com/android/bluetooth/le_scan/ScanManager.java3
-rw-r--r--flags/framework.aconfig10
-rw-r--r--flags/gatt.aconfig11
-rw-r--r--framework/tests/bumble/src/android/bluetooth/pairing/PairingTest.java669
-rw-r--r--framework/tests/bumble/src/android/bluetooth/pairing/utils/IntentReceiver.java400
-rw-r--r--system/bta/Android.bp89
-rw-r--r--system/bta/le_audio/client.cc19
-rw-r--r--system/bta/le_audio/le_audio_client_test.cc24
-rw-r--r--system/bta/le_audio/state_machine.cc2
-rw-r--r--system/bta/le_audio/state_machine_test.cc6
-rw-r--r--system/bta/ras/ras_utils_test.cc179
-rw-r--r--system/gd/rust/topshim/le_audio/le_audio_shim.cc2
-rw-r--r--system/gd/rust/topshim/src/profiles/le_audio.rs23
-rw-r--r--system/include/hardware/bt_le_audio.h1
16 files changed, 1061 insertions, 387 deletions
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 5fc31de514..562f9717c8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -79,6 +79,9 @@
"name": "bluetooth_le_audio_test"
},
{
+ "name": "bluetooth_ras_test"
+ },
+ {
"name": "bluetooth_packet_parser_test"
},
{
@@ -275,6 +278,9 @@
"name": "bluetooth_le_audio_test"
},
{
+ "name": "bluetooth_ras_test"
+ },
+ {
"name": "bluetooth_packet_parser_test"
},
{
diff --git a/android/app/src/com/android/bluetooth/gatt/GattService.java b/android/app/src/com/android/bluetooth/gatt/GattService.java
index d582f63f85..cc2e759f61 100644
--- a/android/app/src/com/android/bluetooth/gatt/GattService.java
+++ b/android/app/src/com/android/bluetooth/gatt/GattService.java
@@ -1505,6 +1505,10 @@ public class GattService extends ProfileService {
unregisterClient(
appId, attributionSource, ContextMap.RemoveReason.REASON_UNREGISTER_ALL);
}
+ for (Integer appId : mServerMap.getAllAppsIds()) {
+ Log.d(TAG, "unreg:" + appId);
+ unregisterServer(appId, attributionSource);
+ }
}
/**************************************************************************
diff --git a/android/app/src/com/android/bluetooth/le_scan/ScanManager.java b/android/app/src/com/android/bluetooth/le_scan/ScanManager.java
index b228bf5fc8..78f759fb13 100644
--- a/android/app/src/com/android/bluetooth/le_scan/ScanManager.java
+++ b/android/app/src/com/android/bluetooth/le_scan/ScanManager.java
@@ -208,7 +208,6 @@ public class ScanManager {
mRegularScanClients.clear();
mBatchClients.clear();
mSuspendedScanClients.clear();
- mScanNative.cleanup();
if (mActivityManager != null) {
try {
@@ -225,6 +224,8 @@ public class ScanManager {
// Shut down the thread
mHandler.removeCallbacksAndMessages(null);
+ mScanNative.cleanup();
+
try {
mAdapterService.unregisterReceiver(mLocationReceiver);
} catch (IllegalArgumentException e) {
diff --git a/flags/framework.aconfig b/flags/framework.aconfig
index 757148b7df..6464f91927 100644
--- a/flags/framework.aconfig
+++ b/flags/framework.aconfig
@@ -86,3 +86,13 @@ flag {
description: "Make BluetoothDevice.ACTION_KEY_MISSING into public API"
bug: "379729762"
}
+
+flag {
+ name: "set_component_available_fix"
+ namespace: "bluetooth"
+ description: "Ensure the state in PackageManager has DISABLED to ENABLED to trigger PACKAGE_CHANGED"
+ bug: "391084450"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/flags/gatt.aconfig b/flags/gatt.aconfig
index 1b9bfb516e..e054fb3f7a 100644
--- a/flags/gatt.aconfig
+++ b/flags/gatt.aconfig
@@ -18,3 +18,14 @@ flag {
bug: "384794418"
is_exported: true
}
+
+flag {
+ name: "advertise_thread"
+ namespace: "bluetooth"
+ description: "Run all advertise functions on a single thread"
+ bug: "391508617"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/framework/tests/bumble/src/android/bluetooth/pairing/PairingTest.java b/framework/tests/bumble/src/android/bluetooth/pairing/PairingTest.java
index 44c6564803..d289b7c43e 100644
--- a/framework/tests/bumble/src/android/bluetooth/pairing/PairingTest.java
+++ b/framework/tests/bumble/src/android/bluetooth/pairing/PairingTest.java
@@ -22,12 +22,8 @@ import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
@@ -43,10 +39,8 @@ import android.bluetooth.StreamObserverSpliterator;
import android.bluetooth.Utils;
import android.bluetooth.test_utils.BlockingBluetoothAdapter;
import android.bluetooth.test_utils.EnableBluetoothRule;
-import android.content.BroadcastReceiver;
+import android.bluetooth.pairing.utils.IntentReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.os.ParcelUuid;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -63,19 +57,15 @@ import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import io.grpc.stub.StreamObserver;
-import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
-import org.hamcrest.core.AllOf;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.hamcrest.MockitoHamcrest;
import pandora.GattProto;
import pandora.HostProto.AdvertiseRequest;
@@ -90,9 +80,6 @@ import pandora.SecurityProto.SecureRequest;
import pandora.SecurityProto.SecureResponse;
import java.time.Duration;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -127,12 +114,9 @@ public class PairingTest {
public final EnableBluetoothRule mEnableBluetoothRule =
new EnableBluetoothRule(false /* enableTestMode */, true /* toggleBluetooth */);
- private final Map<String, Integer> mActionRegistrationCounts = new HashMap<>();
private final StreamObserverSpliterator<PairingEvent> mPairingEventStreamObserver =
new StreamObserverSpliterator<>();
- @Mock private BroadcastReceiver mReceiver;
@Mock private BluetoothProfile.ServiceListener mProfileServiceListener;
- private InOrder mInOrder = null;
private BluetoothDevice mBumbleDevice;
private BluetoothDevice mRemoteLeDevice;
private BluetoothHidHost mHidService;
@@ -142,30 +126,6 @@ public class PairingTest {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- doAnswer(
- inv -> {
- Log.d(
- TAG,
- "onReceive(): intent=" + Arrays.toString(inv.getArguments()));
- Intent intent = inv.getArgument(1);
- String action = intent.getAction();
- if (BluetoothDevice.ACTION_UUID.equals(action)) {
- ParcelUuid[] uuids =
- intent.getParcelableArrayExtra(
- BluetoothDevice.EXTRA_UUID, ParcelUuid.class);
- Log.d(TAG, "onReceive(): UUID=" + Arrays.toString(uuids));
- } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
- int bondState =
- intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
- Log.d(TAG, "onReceive(): bondState=" + bondState);
- }
- return null;
- })
- .when(mReceiver)
- .onReceive(any(), any());
-
- mInOrder = inOrder(mReceiver);
-
// Get profile proxies
mHidService = (BluetoothHidHost) getProfileProxy(BluetoothProfile.HID_HOST);
mHfpService = (BluetoothHeadset) getProfileProxy(BluetoothProfile.HEADSET);
@@ -175,28 +135,54 @@ public class PairingTest {
sAdapter.getRemoteLeDevice(
Utils.BUMBLE_RANDOM_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM);
+ /*
+ * Note: Since there was no IntentReceiver registered, passing the instance as
+ * NULL in testStep_RemoveBond(). But, if there is an instance already present, that
+ * must be passed instead of NULL.
+ */
for (BluetoothDevice device : sAdapter.getBondedDevices()) {
- removeBond(device);
+ testStep_RemoveBond(null, device);
}
}
@After
public void tearDown() throws Exception {
Set<BluetoothDevice> bondedDevices = sAdapter.getBondedDevices();
+
+ /*
+ * Note: Since there was no IntentReceiver registered, passing the instance as
+ * NULL in testStep_RemoveBond(). But, if there is an instance already present, that
+ * must be passed instead of NULL.
+ */
if (bondedDevices.contains(mBumbleDevice)) {
- removeBond(mBumbleDevice);
+ testStep_RemoveBond(null, mBumbleDevice);
}
if (bondedDevices.contains(mRemoteLeDevice)) {
- removeBond(mRemoteLeDevice);
+ testStep_RemoveBond(null, mRemoteLeDevice);
}
mBumbleDevice = null;
mRemoteLeDevice = null;
- if (getTotalActionRegistrationCounts() > 0) {
- sTargetContext.unregisterReceiver(mReceiver);
- mActionRegistrationCounts.clear();
- }
}
+ /** All the test function goes here */
+
+ /**
+ * Process of writing a test function
+ *
+ * 1. Create an IntentReceiver object first with following way:
+ * IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext,
+ * BluetoothDevice.ACTION_1,
+ * BluetoothDevice.ACTION_2)
+ * .setIntentListener(--) // optional
+ * .setIntentTimeout(--) // optional
+ * .build();
+ * 2. Use the intentReceiver instance for all Intent related verification, and pass
+ * the same instance to all the helper/testStep functions which has similar Intent
+ * requirements.
+ * 3. Once all the verification is done, call `intentReceiver.close()` before returning
+ * from the function.
+ */
+
/**
* Test a simple BR/EDR just works pairing flow in the follow steps:
*
@@ -211,8 +197,10 @@ public class PairingTest {
*/
@Test
public void testBrEdrPairing_phoneInitiatedBrEdrInquiryOnlyJustWorks() {
- registerIntentActions(
- BluetoothDevice.ACTION_BOND_STATE_CHANGED, BluetoothDevice.ACTION_PAIRING_REQUEST);
+ IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext,
+ BluetoothDevice.ACTION_BOND_STATE_CHANGED,
+ BluetoothDevice.ACTION_PAIRING_REQUEST)
+ .build();
StreamObserver<PairingEventAnswer> pairingEventAnswerObserver =
mBumble.security()
@@ -220,12 +208,12 @@ public class PairingTest {
.onPairing(mPairingEventStreamObserver);
assertThat(mBumbleDevice.createBond()).isTrue();
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING));
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(
@@ -238,15 +226,12 @@ public class PairingTest {
pairingEventAnswerObserver.onNext(
PairingEventAnswer.newBuilder().setEvent(pairingEvent).setConfirm(true).build());
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED));
- verifyNoMoreInteractions(mReceiver);
-
- unregisterIntentActions(
- BluetoothDevice.ACTION_BOND_STATE_CHANGED, BluetoothDevice.ACTION_PAIRING_REQUEST);
+ intentReceiver.close();
}
/**
@@ -266,8 +251,10 @@ public class PairingTest {
@Test
@RequiresFlagsEnabled({Flags.FLAG_IGNORE_UNRELATED_CANCEL_BOND})
public void testBrEdrPairing_cancelBond_forUnrelatedDevice() {
- registerIntentActions(
- BluetoothDevice.ACTION_BOND_STATE_CHANGED, BluetoothDevice.ACTION_PAIRING_REQUEST);
+ IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext,
+ BluetoothDevice.ACTION_BOND_STATE_CHANGED,
+ BluetoothDevice.ACTION_PAIRING_REQUEST)
+ .build();
StreamObserver<PairingEventAnswer> pairingEventAnswerObserver =
mBumble.security()
@@ -275,12 +262,12 @@ public class PairingTest {
.onPairing(mPairingEventStreamObserver);
assertThat(mBumbleDevice.createBond()).isTrue();
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING));
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(
@@ -296,15 +283,12 @@ public class PairingTest {
pairingEventAnswerObserver.onNext(
PairingEventAnswer.newBuilder().setEvent(pairingEvent).setConfirm(true).build());
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED));
- verifyNoMoreInteractions(mReceiver);
-
- unregisterIntentActions(
- BluetoothDevice.ACTION_BOND_STATE_CHANGED, BluetoothDevice.ACTION_PAIRING_REQUEST);
+ intentReceiver.close();
}
/**
@@ -322,10 +306,11 @@ public class PairingTest {
*/
@Test
public void testBrEdrPairing_phoneInitiatedBrEdrInquiryOnlyJustWorksWhileSdpConnected() {
- registerIntentActions(
+ IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext,
BluetoothDevice.ACTION_ACL_CONNECTED,
BluetoothDevice.ACTION_BOND_STATE_CHANGED,
- BluetoothDevice.ACTION_PAIRING_REQUEST);
+ BluetoothDevice.ACTION_PAIRING_REQUEST)
+ .build();
StreamObserver<PairingEventAnswer> pairingEventAnswerObserver =
mBumble.security()
@@ -335,17 +320,17 @@ public class PairingTest {
// Start SDP. This will create an ACL connection before the bonding starts.
assertThat(mBumbleDevice.fetchUuidsWithSdp(BluetoothDevice.TRANSPORT_BREDR)).isTrue();
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_ACL_CONNECTED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice));
assertThat(mBumbleDevice.createBond()).isTrue();
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING));
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(
@@ -358,17 +343,12 @@ public class PairingTest {
pairingEventAnswerObserver.onNext(
PairingEventAnswer.newBuilder().setEvent(pairingEvent).setConfirm(true).build());
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED));
- verifyNoMoreInteractions(mReceiver);
-
- unregisterIntentActions(
- BluetoothDevice.ACTION_ACL_CONNECTED,
- BluetoothDevice.ACTION_BOND_STATE_CHANGED,
- BluetoothDevice.ACTION_PAIRING_REQUEST);
+ intentReceiver.close();
}
/**
@@ -398,11 +378,13 @@ public class PairingTest {
@Test
@RequiresFlagsEnabled({Flags.FLAG_PREVENT_DUPLICATE_UUID_INTENT})
public void testCancelBondLe_WithGattServiceDiscovery() {
- registerIntentActions(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext,
+ BluetoothDevice.ACTION_BOND_STATE_CHANGED)
+ .build();
// Outgoing GATT service discovery and incoming LE pairing in parallel
StreamObserverSpliterator<SecureResponse> responseObserver =
- helper_OutgoingGattServiceDiscoveryWithIncomingLePairing();
+ helper_OutgoingGattServiceDiscoveryWithIncomingLePairing(intentReceiver);
// Cancel pairing from Android
assertThat(mBumbleDevice.cancelBondProcess()).isTrue();
@@ -412,14 +394,12 @@ public class PairingTest {
// Pairing should be cancelled in a moment instead of timing out in 30
// seconds
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE));
- verifyNoMoreInteractions(mReceiver);
-
- unregisterIntentActions(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ intentReceiver.close();
}
/**
@@ -449,11 +429,13 @@ public class PairingTest {
@Test
@RequiresFlagsEnabled({Flags.FLAG_PREVENT_DUPLICATE_UUID_INTENT})
public void testBondLe_WithGattServiceDiscovery() {
- registerIntentActions(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext,
+ BluetoothDevice.ACTION_BOND_STATE_CHANGED)
+ .build();
// Outgoing GATT service discovery and incoming LE pairing in parallel
StreamObserverSpliterator<SecureResponse> responseObserver =
- helper_OutgoingGattServiceDiscoveryWithIncomingLePairing();
+ helper_OutgoingGattServiceDiscoveryWithIncomingLePairing(intentReceiver);
// Approve pairing from Android
assertThat(mBumbleDevice.setPairingConfirmation(true)).isTrue();
@@ -462,14 +444,12 @@ public class PairingTest {
assertThat(secureResponse.hasSuccess()).isTrue();
// Ensure that pairing succeeds
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED));
- verifyNoMoreInteractions(mReceiver);
-
- unregisterIntentActions(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ intentReceiver.close();
}
/**
@@ -495,9 +475,11 @@ public class PairingTest {
*/
@Test
public void testBondLe_Reconnect() {
- registerIntentActions(BluetoothDevice.ACTION_ACL_CONNECTED);
+ IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext,
+ BluetoothDevice.ACTION_ACL_CONNECTED)
+ .build();
- testStep_BondLe(mBumbleDevice, OwnAddressType.PUBLIC);
+ testStep_BondLe(intentReceiver, mBumbleDevice, OwnAddressType.PUBLIC);
assertThat(sAdapter.getBondedDevices()).contains(mBumbleDevice);
testStep_restartBt();
@@ -521,12 +503,12 @@ public class PairingTest {
.build());
assertThat(mBumbleDevice.connect()).isEqualTo(BluetoothStatusCodes.SUCCESS);
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_ACL_CONNECTED),
hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_LE),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice));
- verifyNoMoreInteractions(mReceiver);
- unregisterIntentActions(BluetoothDevice.ACTION_ACL_CONNECTED);
+
+ intentReceiver.close();
}
/**
@@ -559,98 +541,6 @@ public class PairingTest {
}
}
- private void doTestIdentityAddressWithType(
- BluetoothDevice device, OwnAddressType ownAddressType) {
- BluetoothAddress identityAddress = device.getIdentityAddressWithType();
- assertThat(identityAddress.getAddress()).isNull();
- assertThat(identityAddress.getAddressType())
- .isEqualTo(BluetoothDevice.ADDRESS_TYPE_UNKNOWN);
-
- testStep_BondLe(device, ownAddressType);
- assertThat(sAdapter.getBondedDevices()).contains(device);
-
- identityAddress = device.getIdentityAddressWithType();
- assertThat(identityAddress.getAddress()).isEqualTo(device.getAddress());
- assertThat(identityAddress.getAddressType())
- .isEqualTo(
- ownAddressType == OwnAddressType.RANDOM
- ? BluetoothDevice.ADDRESS_TYPE_RANDOM
- : BluetoothDevice.ADDRESS_TYPE_PUBLIC);
- }
-
- private void testStep_BondLe(BluetoothDevice device, OwnAddressType ownAddressType) {
- registerIntentActions(
- BluetoothDevice.ACTION_BOND_STATE_CHANGED,
- BluetoothDevice.ACTION_ACL_CONNECTED,
- BluetoothDevice.ACTION_PAIRING_REQUEST);
-
- mBumble.gattBlocking()
- .registerService(
- GattProto.RegisterServiceRequest.newBuilder()
- .setService(
- GattProto.GattServiceParams.newBuilder()
- .setUuid(BATTERY_UUID.toString())
- .build())
- .build());
- mBumble.gattBlocking()
- .registerService(
- GattProto.RegisterServiceRequest.newBuilder()
- .setService(
- GattProto.GattServiceParams.newBuilder()
- .setUuid(HOGP_UUID.toString())
- .build())
- .build());
-
- mBumble.hostBlocking()
- .advertise(
- AdvertiseRequest.newBuilder()
- .setLegacy(true)
- .setConnectable(true)
- .setOwnAddressType(ownAddressType)
- .build());
-
- StreamObserver<PairingEventAnswer> pairingEventAnswerObserver =
- mBumble.security()
- .withDeadlineAfter(BOND_INTENT_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS)
- .onPairing(mPairingEventStreamObserver);
-
- assertThat(device.createBond(BluetoothDevice.TRANSPORT_LE)).isTrue();
-
- verifyIntentReceivedUnordered(
- hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
- hasExtra(BluetoothDevice.EXTRA_DEVICE, device),
- hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING));
- verifyIntentReceived(
- hasAction(BluetoothDevice.ACTION_ACL_CONNECTED),
- hasExtra(BluetoothDevice.EXTRA_DEVICE, device),
- hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_LE));
- verifyIntentReceivedUnordered(
- hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST),
- hasExtra(BluetoothDevice.EXTRA_DEVICE, device),
- hasExtra(
- BluetoothDevice.EXTRA_PAIRING_VARIANT,
- BluetoothDevice.PAIRING_VARIANT_CONSENT));
-
- // Approve pairing from Android
- assertThat(device.setPairingConfirmation(true)).isTrue();
-
- PairingEvent pairingEvent = mPairingEventStreamObserver.iterator().next();
- assertThat(pairingEvent.hasJustWorks()).isTrue();
- pairingEventAnswerObserver.onNext(
- PairingEventAnswer.newBuilder().setEvent(pairingEvent).setConfirm(true).build());
-
- // Ensure that pairing succeeds
- verifyIntentReceived(
- hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
- hasExtra(BluetoothDevice.EXTRA_DEVICE, device),
- hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED));
-
- unregisterIntentActions(
- BluetoothDevice.ACTION_BOND_STATE_CHANGED,
- BluetoothDevice.ACTION_ACL_CONNECTED,
- BluetoothDevice.ACTION_PAIRING_REQUEST);
- }
-
/**
* Test if bonded BR/EDR device can reconnect after BT restart
*
@@ -674,9 +564,11 @@ public class PairingTest {
*/
@Test
public void testBondBredr_Reconnect() {
- registerIntentActions(BluetoothDevice.ACTION_ACL_CONNECTED);
+ IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext,
+ BluetoothDevice.ACTION_ACL_CONNECTED)
+ .build();
- testStep_BondBredr();
+ testStep_BondBredr(intentReceiver);
assertThat(sAdapter.getBondedDevices()).contains(mBumbleDevice);
testStep_restartBt();
@@ -689,12 +581,12 @@ public class PairingTest {
.build();
mBumble.hostBlocking().setConnectabilityMode(request);
assertThat(mBumbleDevice.connect()).isEqualTo(BluetoothStatusCodes.SUCCESS);
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_ACL_CONNECTED),
hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_BREDR),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice));
- verifyNoMoreInteractions(mReceiver);
- unregisterIntentActions(BluetoothDevice.ACTION_ACL_CONNECTED);
+
+ intentReceiver.close();
}
/**
@@ -720,27 +612,27 @@ public class PairingTest {
@Test
@RequiresFlagsEnabled({Flags.FLAG_WAIT_FOR_DISCONNECT_BEFORE_UNBOND})
public void testRemoveBondLe_WhenConnected() {
- registerIntentActions(
- BluetoothDevice.ACTION_ACL_DISCONNECTED, BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext,
+ BluetoothDevice.ACTION_ACL_DISCONNECTED,
+ BluetoothDevice.ACTION_BOND_STATE_CHANGED)
+ .build();
- testStep_BondLe(mBumbleDevice, OwnAddressType.PUBLIC);
+ testStep_BondLe(intentReceiver, mBumbleDevice, OwnAddressType.PUBLIC);
assertThat(sAdapter.getBondedDevices()).contains(mBumbleDevice);
assertThat(mBumbleDevice.removeBond()).isTrue();
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_ACL_DISCONNECTED),
hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_LE),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice));
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE));
assertThat(sAdapter.getBondedDevices()).doesNotContain(mBumbleDevice);
- verifyNoMoreInteractions(mReceiver);
- unregisterIntentActions(
- BluetoothDevice.ACTION_ACL_DISCONNECTED, BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ intentReceiver.close();
}
/**
@@ -766,27 +658,27 @@ public class PairingTest {
@Test
@RequiresFlagsEnabled({Flags.FLAG_WAIT_FOR_DISCONNECT_BEFORE_UNBOND})
public void testRemoveBondBredr_WhenConnected() {
- registerIntentActions(
- BluetoothDevice.ACTION_ACL_DISCONNECTED, BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext,
+ BluetoothDevice.ACTION_ACL_DISCONNECTED,
+ BluetoothDevice.ACTION_BOND_STATE_CHANGED)
+ .build();
- testStep_BondBredr();
+ testStep_BondBredr(intentReceiver);
assertThat(sAdapter.getBondedDevices()).contains(mBumbleDevice);
assertThat(mBumbleDevice.removeBond()).isTrue();
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_ACL_DISCONNECTED),
hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_BREDR),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice));
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE));
assertThat(sAdapter.getBondedDevices()).doesNotContain(mBumbleDevice);
- verifyNoMoreInteractions(mReceiver);
- unregisterIntentActions(
- BluetoothDevice.ACTION_ACL_DISCONNECTED, BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ intentReceiver.close();
}
/**
@@ -813,54 +705,51 @@ public class PairingTest {
*/
@Test
public void testRemoveBondLe_WhenDisconnected() {
- registerIntentActions(
+ IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext,
BluetoothDevice.ACTION_ACL_DISCONNECTED,
BluetoothDevice.ACTION_BOND_STATE_CHANGED,
- BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
+ BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED)
+ .build();
- testStep_BondLe(mBumbleDevice, OwnAddressType.PUBLIC);
+ testStep_BondLe(intentReceiver, mBumbleDevice, OwnAddressType.PUBLIC);
assertThat(sAdapter.getBondedDevices()).contains(mBumbleDevice);
// Wait for profiles to get connected
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothHidHost.EXTRA_STATE, BluetoothHidHost.STATE_CONNECTING));
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothHidHost.EXTRA_STATE, BluetoothHidHost.STATE_CONNECTED));
// Disconnect Bumble
assertThat(mBumbleDevice.disconnect()).isEqualTo(BluetoothStatusCodes.SUCCESS);
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothHidHost.EXTRA_STATE, BluetoothHidHost.STATE_DISCONNECTING));
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothHidHost.EXTRA_STATE, BluetoothHidHost.STATE_DISCONNECTED));
// Wait for ACL to get disconnected
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_ACL_DISCONNECTED),
hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_LE),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice));
// Remove bond
assertThat(mBumbleDevice.removeBond()).isTrue();
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE));
assertThat(sAdapter.getBondedDevices()).doesNotContain(mBumbleDevice);
- verifyNoMoreInteractions(mReceiver);
- unregisterIntentActions(
- BluetoothDevice.ACTION_ACL_DISCONNECTED,
- BluetoothDevice.ACTION_BOND_STATE_CHANGED,
- BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
+ intentReceiver.close();
}
/**
@@ -887,10 +776,11 @@ public class PairingTest {
*/
@Test
public void testRemoveBondBredr_WhenDisconnected() {
- registerIntentActions(
+ IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext,
BluetoothDevice.ACTION_ACL_DISCONNECTED,
BluetoothDevice.ACTION_BOND_STATE_CHANGED,
- BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)
+ .build();
// Disable all profiles other than A2DP as profile connections take too long
assertThat(
@@ -902,15 +792,15 @@ public class PairingTest {
mBumbleDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN))
.isTrue();
- testStep_BondBredr();
+ testStep_BondBredr(intentReceiver);
assertThat(sAdapter.getBondedDevices()).contains(mBumbleDevice);
// Wait for profiles to get connected
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED),
hasExtra(BluetoothA2dp.EXTRA_STATE, BluetoothA2dp.STATE_CONNECTING),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice));
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED),
hasExtra(BluetoothA2dp.EXTRA_STATE, BluetoothA2dp.STATE_CONNECTED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice));
@@ -921,58 +811,78 @@ public class PairingTest {
future.completeOnTimeout(null, TEST_DELAY_MS, TimeUnit.MILLISECONDS).join();
// Disconnect all profiles
assertThat(mBumbleDevice.disconnect()).isEqualTo(BluetoothStatusCodes.SUCCESS);
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED),
hasExtra(BluetoothA2dp.EXTRA_STATE, BluetoothA2dp.STATE_DISCONNECTING),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice));
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED),
hasExtra(BluetoothA2dp.EXTRA_STATE, BluetoothA2dp.STATE_DISCONNECTED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice));
// Wait for the ACL to get disconnected
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_ACL_DISCONNECTED),
hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_BREDR),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice));
// Remove bond
assertThat(mBumbleDevice.removeBond()).isTrue();
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE));
assertThat(sAdapter.getBondedDevices()).doesNotContain(mBumbleDevice);
- verifyNoMoreInteractions(mReceiver);
- unregisterIntentActions(
- BluetoothDevice.ACTION_ACL_DISCONNECTED,
- BluetoothDevice.ACTION_BOND_STATE_CHANGED,
- BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ intentReceiver.close();
}
- private void testStep_BondBredr() {
- registerIntentActions(
+ /** Helper/testStep functions goes here */
+
+ /**
+ * Process of writing a helper/test_step function.
+ *
+ * 1. All the helper functions should have IntentReceiver instance passed as an
+ * argument to them (if any intents needs to be registered).
+ * 2. The caller (if a test function) can initiate a fresh instance of IntentReceiver
+ * and use it for all subsequent helper/testStep functions.
+ * 3. The helper function should first register all required intent actions through the
+ * helper -> IntentReceiver.updateNewIntentActionsInParentReceiver()
+ * which either modifies the intentReceiver instance, or creates
+ * one (if the caller has passed a `null`).
+ * 4. At the end, all functions should call `intentReceiver.close()` which either
+ * unregisters the recent actions, or frees the original instance as per the call.
+ */
+
+ private void testStep_BondBredr(IntentReceiver parentIntentReceiver) {
+ IntentReceiver intentReceiver =
+ IntentReceiver.updateNewIntentActionsInParentReceiver(
+ parentIntentReceiver,
+ sTargetContext,
BluetoothDevice.ACTION_BOND_STATE_CHANGED,
BluetoothDevice.ACTION_ACL_CONNECTED,
BluetoothDevice.ACTION_PAIRING_REQUEST);
StreamObserver<PairingEventAnswer> pairingEventAnswerObserver =
mBumble.security()
- .withDeadlineAfter(BOND_INTENT_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS)
+ .withDeadlineAfter(BOND_INTENT_TIMEOUT.toMillis(),
+ TimeUnit.MILLISECONDS)
.onPairing(mPairingEventStreamObserver);
- assertThat(mBumbleDevice.createBond(BluetoothDevice.TRANSPORT_BREDR)).isTrue();
+ assertThat(mBumbleDevice.createBond(BluetoothDevice.TRANSPORT_BREDR)).
+ isTrue();
- verifyIntentReceivedUnordered(
+ intentReceiver.verifyReceived(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
- hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING));
- verifyIntentReceived(
+ hasExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.BOND_BONDING));
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_ACL_CONNECTED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
- hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_BREDR));
- verifyIntentReceivedUnordered(
+ hasExtra(BluetoothDevice.EXTRA_TRANSPORT,
+ BluetoothDevice.TRANSPORT_BREDR));
+ intentReceiver.verifyReceived(
hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(
@@ -985,18 +895,18 @@ public class PairingTest {
PairingEvent pairingEvent = mPairingEventStreamObserver.iterator().next();
assertThat(pairingEvent.hasJustWorks()).isTrue();
pairingEventAnswerObserver.onNext(
- PairingEventAnswer.newBuilder().setEvent(pairingEvent).setConfirm(true).build());
+ PairingEventAnswer.newBuilder().setEvent(pairingEvent)
+ .setConfirm(true).build());
// Ensure that pairing succeeds
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
- hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDED));
+ hasExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.BOND_BONDED));
- unregisterIntentActions(
- BluetoothDevice.ACTION_BOND_STATE_CHANGED,
- BluetoothDevice.ACTION_ACL_CONNECTED,
- BluetoothDevice.ACTION_PAIRING_REQUEST);
+ /* Unregisters all intent actions registered in this function */
+ intentReceiver.close();
}
private void testStep_restartBt() {
@@ -1006,9 +916,13 @@ public class PairingTest {
/* Starts outgoing GATT service discovery and incoming LE pairing in parallel */
private StreamObserverSpliterator<SecureResponse>
- helper_OutgoingGattServiceDiscoveryWithIncomingLePairing() {
- // Setup intent filters
- registerIntentActions(
+ helper_OutgoingGattServiceDiscoveryWithIncomingLePairing(
+ IntentReceiver parentIntentReceiver) {
+ // Register new actions specific to this helper function
+ IntentReceiver intentReceiver =
+ IntentReceiver.updateNewIntentActionsInParentReceiver(
+ parentIntentReceiver,
+ sTargetContext,
BluetoothDevice.ACTION_BOND_STATE_CHANGED,
BluetoothDevice.ACTION_PAIRING_REQUEST,
BluetoothDevice.ACTION_UUID,
@@ -1027,7 +941,8 @@ public class PairingTest {
}
// Start GATT service discovery, this will establish LE ACL
- assertThat(mBumbleDevice.fetchUuidsWithSdp(BluetoothDevice.TRANSPORT_LE)).isTrue();
+ assertThat(mBumbleDevice.fetchUuidsWithSdp(BluetoothDevice.TRANSPORT_LE))
+ .isTrue();
// Make Bumble connectable
AdvertiseResponse advertiseResponse =
@@ -1041,12 +956,13 @@ public class PairingTest {
.next();
// Todo: Unexpected empty ACTION_UUID intent is generated
- verifyIntentReceivedUnordered(hasAction(BluetoothDevice.ACTION_UUID));
+ intentReceiver.verifyReceived(hasAction(BluetoothDevice.ACTION_UUID));
// Wait for connection on Android
- verifyIntentReceivedUnordered(
+ intentReceiver.verifyReceived(
hasAction(BluetoothDevice.ACTION_ACL_CONNECTED),
- hasExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_LE));
+ hasExtra(BluetoothDevice.EXTRA_TRANSPORT,
+ BluetoothDevice.TRANSPORT_LE));
// Start pairing from Bumble
StreamObserverSpliterator<SecureResponse> responseObserver =
@@ -1061,11 +977,12 @@ public class PairingTest {
// Wait for incoming pairing notification on Android
// TODO: Order of these events is not deterministic
- verifyIntentReceivedUnordered(
+ intentReceiver.verifyReceived(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
- hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_BONDING));
- verifyIntentReceivedUnordered(
+ hasExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.BOND_BONDING));
+ intentReceiver.verifyReceived(
hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
hasExtra(
@@ -1076,7 +993,7 @@ public class PairingTest {
assertThat(mBumbleDevice.setPairingConfirmation(true)).isTrue();
// Wait for pairing approval notification on Android
- verifyIntentReceivedUnordered(
+ intentReceiver.verifyReceived(
2,
hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST),
hasExtra(BluetoothDevice.EXTRA_DEVICE, mBumbleDevice),
@@ -1086,134 +1003,142 @@ public class PairingTest {
// Wait for GATT service discovery to complete on Android
// so that ACTION_UUID is received here.
- verifyIntentReceivedUnordered(
+ intentReceiver.verifyReceived(
hasAction(BluetoothDevice.ACTION_UUID),
- hasExtra(BluetoothDevice.EXTRA_UUID, Matchers.hasItemInArray(BATTERY_UUID)));
-
- unregisterIntentActions(
- BluetoothDevice.ACTION_BOND_STATE_CHANGED,
- BluetoothDevice.ACTION_PAIRING_REQUEST,
- BluetoothDevice.ACTION_UUID,
- BluetoothDevice.ACTION_ACL_CONNECTED);
+ hasExtra(BluetoothDevice.EXTRA_UUID,
+ Matchers.hasItemInArray(BATTERY_UUID)));
+ intentReceiver.close();
return responseObserver;
}
- private void removeBond(BluetoothDevice device) {
- registerIntentActions(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ private void testStep_RemoveBond(IntentReceiver parentIntentReceiver,
+ BluetoothDevice device) {
+ IntentReceiver intentReceiver =
+ IntentReceiver.updateNewIntentActionsInParentReceiver(
+ parentIntentReceiver,
+ sTargetContext,
+ BluetoothDevice.ACTION_BOND_STATE_CHANGED);
assertThat(device.removeBond()).isTrue();
- verifyIntentReceived(
+ intentReceiver.verifyReceivedOrdered(
hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
hasExtra(BluetoothDevice.EXTRA_DEVICE, device),
- hasExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE));
+ hasExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.BOND_NONE));
- unregisterIntentActions(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ intentReceiver.close();
}
- @SafeVarargs
- private void verifyIntentReceived(Matcher<Intent>... matchers) {
- mInOrder.verify(mReceiver, timeout(BOND_INTENT_TIMEOUT.toMillis()))
- .onReceive(any(Context.class), MockitoHamcrest.argThat(AllOf.allOf(matchers)));
+ private BluetoothProfile getProfileProxy(int profile) {
+ sAdapter.getProfileProxy(sTargetContext, mProfileServiceListener, profile);
+ ArgumentCaptor<BluetoothProfile> proxyCaptor =
+ ArgumentCaptor.forClass(BluetoothProfile.class);
+ verify(mProfileServiceListener, timeout(BOND_INTENT_TIMEOUT.toMillis()))
+ .onServiceConnected(eq(profile), proxyCaptor.capture());
+ return proxyCaptor.getValue();
}
- @SafeVarargs
- private void verifyIntentReceivedUnordered(int num, Matcher<Intent>... matchers) {
- verify(mReceiver, timeout(BOND_INTENT_TIMEOUT.toMillis()).times(num))
- .onReceive(any(Context.class), MockitoHamcrest.argThat(AllOf.allOf(matchers)));
- }
+ private void testStep_BondLe(IntentReceiver parentIntentReceiver,
+ BluetoothDevice device, OwnAddressType ownAddressType) {
+ IntentReceiver intentReceiver =
+ IntentReceiver.updateNewIntentActionsInParentReceiver(
+ parentIntentReceiver,
+ sTargetContext,
+ BluetoothDevice.ACTION_BOND_STATE_CHANGED,
+ BluetoothDevice.ACTION_ACL_CONNECTED,
+ BluetoothDevice.ACTION_PAIRING_REQUEST);
- @SafeVarargs
- private void verifyIntentReceivedUnordered(Matcher<Intent>... matchers) {
- verifyIntentReceivedUnordered(1, matchers);
- }
+ mBumble.gattBlocking()
+ .registerService(
+ GattProto.RegisterServiceRequest.newBuilder()
+ .setService(
+ GattProto.GattServiceParams.newBuilder()
+ .setUuid(BATTERY_UUID.toString())
+ .build())
+ .build());
+ mBumble.gattBlocking()
+ .registerService(
+ GattProto.RegisterServiceRequest.newBuilder()
+ .setService(
+ GattProto.GattServiceParams.newBuilder()
+ .setUuid(HOGP_UUID.toString())
+ .build())
+ .build());
- /**
- * Helper function to add reference count to registered intent actions
- *
- * @param actions new intent actions to add. If the array is empty, it is a no-op.
- */
- private void registerIntentActions(String... actions) {
- if (actions.length == 0) {
- return;
- }
- if (getTotalActionRegistrationCounts() > 0) {
- Log.d(TAG, "registerIntentActions(): unregister ALL intents");
- sTargetContext.unregisterReceiver(mReceiver);
- }
- for (String action : actions) {
- mActionRegistrationCounts.merge(action, 1, Integer::sum);
- }
- IntentFilter filter = new IntentFilter();
- mActionRegistrationCounts.entrySet().stream()
- .filter(entry -> entry.getValue() > 0)
- .forEach(
- entry -> {
- Log.d(
- TAG,
- "registerIntentActions(): Registering action = "
- + entry.getKey());
- filter.addAction(entry.getKey());
- });
- sTargetContext.registerReceiver(mReceiver, filter);
- }
+ mBumble.hostBlocking()
+ .advertise(
+ AdvertiseRequest.newBuilder()
+ .setLegacy(true)
+ .setConnectable(true)
+ .setOwnAddressType(ownAddressType)
+ .build());
- /**
- * Helper function to reduce reference count to registered intent actions If total reference
- * count is zero after removal, no broadcast receiver will be registered.
- *
- * @param actions intent actions to be removed. If some action is not registered, it is no-op
- * for that action. If the actions array is empty, it is also a no-op.
- */
- private void unregisterIntentActions(String... actions) {
- if (actions.length == 0) {
- return;
- }
- if (getTotalActionRegistrationCounts() <= 0) {
- return;
- }
- Log.d(TAG, "unregisterIntentActions(): unregister ALL intents");
- sTargetContext.unregisterReceiver(mReceiver);
- for (String action : actions) {
- if (!mActionRegistrationCounts.containsKey(action)) {
- continue;
- }
- mActionRegistrationCounts.put(action, mActionRegistrationCounts.get(action) - 1);
- if (mActionRegistrationCounts.get(action) <= 0) {
- mActionRegistrationCounts.remove(action);
- }
- }
- if (getTotalActionRegistrationCounts() > 0) {
- IntentFilter filter = new IntentFilter();
- mActionRegistrationCounts.entrySet().stream()
- .filter(entry -> entry.getValue() > 0)
- .forEach(
- entry -> {
- Log.d(
- TAG,
- "unregisterIntentActions(): Registering action = "
- + entry.getKey());
- filter.addAction(entry.getKey());
- });
- sTargetContext.registerReceiver(mReceiver, filter);
- }
- }
+ StreamObserver<PairingEventAnswer> pairingEventAnswerObserver =
+ mBumble.security()
+ .withDeadlineAfter(BOND_INTENT_TIMEOUT.toMillis(),
+ TimeUnit.MILLISECONDS)
+ .onPairing(mPairingEventStreamObserver);
- /**
- * Get sum of reference count from all registered actions
- *
- * @return sum of reference count from all registered actions
- */
- private int getTotalActionRegistrationCounts() {
- return mActionRegistrationCounts.values().stream().reduce(0, Integer::sum);
+ assertThat(device.createBond(BluetoothDevice.TRANSPORT_LE)).isTrue();
+
+ intentReceiver.verifyReceived(
+ hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
+ hasExtra(BluetoothDevice.EXTRA_DEVICE, device),
+ hasExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.BOND_BONDING));
+ intentReceiver.verifyReceivedOrdered(
+ hasAction(BluetoothDevice.ACTION_ACL_CONNECTED),
+ hasExtra(BluetoothDevice.EXTRA_DEVICE, device),
+ hasExtra(BluetoothDevice.EXTRA_TRANSPORT,
+ BluetoothDevice.TRANSPORT_LE));
+ intentReceiver.verifyReceived(
+ hasAction(BluetoothDevice.ACTION_PAIRING_REQUEST),
+ hasExtra(BluetoothDevice.EXTRA_DEVICE, device),
+ hasExtra(
+ BluetoothDevice.EXTRA_PAIRING_VARIANT,
+ BluetoothDevice.PAIRING_VARIANT_CONSENT));
+
+ // Approve pairing from Android
+ assertThat(device.setPairingConfirmation(true)).isTrue();
+
+ PairingEvent pairingEvent = mPairingEventStreamObserver.iterator().next();
+ assertThat(pairingEvent.hasJustWorks()).isTrue();
+ pairingEventAnswerObserver.onNext(
+ PairingEventAnswer.newBuilder().setEvent(pairingEvent)
+ .setConfirm(true).build());
+
+ // Ensure that pairing succeeds
+ intentReceiver.verifyReceivedOrdered(
+ hasAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED),
+ hasExtra(BluetoothDevice.EXTRA_DEVICE, device),
+ hasExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.BOND_BONDED));
+
+ intentReceiver.close();
}
- private BluetoothProfile getProfileProxy(int profile) {
- sAdapter.getProfileProxy(sTargetContext, mProfileServiceListener, profile);
- ArgumentCaptor<BluetoothProfile> proxyCaptor =
- ArgumentCaptor.forClass(BluetoothProfile.class);
- verify(mProfileServiceListener, timeout(BOND_INTENT_TIMEOUT.toMillis()))
- .onServiceConnected(eq(profile), proxyCaptor.capture());
- return proxyCaptor.getValue();
+ private void doTestIdentityAddressWithType(BluetoothDevice device,
+ OwnAddressType ownAddressType) {
+ BluetoothAddress identityAddress = device.getIdentityAddressWithType();
+ assertThat(identityAddress.getAddress()).isNull();
+ assertThat(identityAddress.getAddressType())
+ .isEqualTo(BluetoothDevice.ADDRESS_TYPE_UNKNOWN);
+
+ /*
+ * Note: Since there was no IntentReceiver registered, passing the
+ * instance as NULL. But, if there is an instance already present, that
+ * must be passed instead of NULL.
+ */
+ testStep_BondLe(null, device, ownAddressType);
+ assertThat(sAdapter.getBondedDevices()).contains(device);
+
+ identityAddress = device.getIdentityAddressWithType();
+ assertThat(identityAddress.getAddress()).isEqualTo(device.getAddress());
+ assertThat(identityAddress.getAddressType())
+ .isEqualTo(
+ ownAddressType == OwnAddressType.RANDOM
+ ? BluetoothDevice.ADDRESS_TYPE_RANDOM
+ : BluetoothDevice.ADDRESS_TYPE_PUBLIC);
}
}
diff --git a/framework/tests/bumble/src/android/bluetooth/pairing/utils/IntentReceiver.java b/framework/tests/bumble/src/android/bluetooth/pairing/utils/IntentReceiver.java
new file mode 100644
index 0000000000..ef8ab310dd
--- /dev/null
+++ b/framework/tests/bumble/src/android/bluetooth/pairing/utils/IntentReceiver.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.pairing.utils;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import com.google.common.collect.Iterators;
+import org.hamcrest.Matcher;
+import org.hamcrest.core.AllOf;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.hamcrest.MockitoHamcrest;
+
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+
+/**
+ * IntentReceiver helps in managing the Intents received through the Broadcast
+ * receiver, with specific intent actions registered.
+ * It uses Builder pattern for instance creation, and also allows setting up
+ * a custom listener's onReceive().
+ *
+ * Use the following way to create an instance of the IntentReceiver.
+ * IntentReceiver intentReceiver = new IntentReceiver.Builder(sTargetContext,
+ * BluetoothDevice.ACTION_1,
+ * BluetoothDevice.ACTION_2)
+ * .setIntentListener(--) // optional
+ * .setIntentTimeout(--) // optional
+ * .build();
+ *
+ * Ordered and unordered verification mechanisms are also provided through public methods.
+ */
+
+public class IntentReceiver {
+ private static final String TAG = IntentReceiver.class.getSimpleName();
+
+ /** Interface for listening & processing the received intents */
+ public interface IntentListener {
+ /**
+ * Callback for receiving intents
+ *
+ * @param intent Received intent
+ */
+ void onReceive(Intent intent);
+ }
+
+ @Mock private BroadcastReceiver mReceiver;
+
+ /** Intent timeout value, can be configured through constructor, or setter method */
+ private final Duration mIntentTimeout;
+
+ /** To verify the received intents in-order */
+ private final InOrder mInOrder;
+ private final Context mContext;
+ private final String[] mIntentStrings;
+ private final Deque<IntentFilter> mDqIntentFilter;
+ /*
+ * Note: Since we are using Builder pattern, also add new variables added
+ * to the Builder class
+ */
+
+ /** Listener for the received intents */
+ private final IntentListener mIntentListener;
+
+ /**
+ * Creates an Intent receiver from the builder instance
+ * Note: This is a private constructor, so always prepare IntentReceiver's
+ * instance through Builder().
+ *
+ * @param builder Pre-built builder instance
+ */
+ private IntentReceiver(Builder builder) {
+ this.mIntentTimeout = builder.mIntentTimeout;
+ this.mContext = builder.mContext;
+ this.mIntentStrings = builder.mIntentStrings;
+ this.mIntentListener = builder.mIntentListener;
+
+ /* Perform other calls required for instantiation */
+ MockitoAnnotations.initMocks(this);
+ mInOrder = inOrder(mReceiver);
+ mDqIntentFilter = new ArrayDeque<>();
+ mDqIntentFilter.addFirst(prepareIntentFilter(mIntentStrings));
+
+ setupListener();
+ registerReceiver();
+ }
+
+ /** Private constructor to avoid creation of IntentReceiver instance directly */
+ private IntentReceiver() {
+ mIntentTimeout = null;
+ mInOrder = null;
+ mContext = null;
+ mIntentStrings = null;
+ mDqIntentFilter = null;
+ mIntentListener = null;
+ }
+
+ /**
+ * Builder class which helps in avoiding overloading constructors (as the class grows)
+ * Usage:
+ * new IntentReceiver.Builder(ARGS)
+ * .setterMethods() **Optional calls, as these are default params
+ * .build();
+ */
+ public static class Builder {
+ /**
+ * Add all the instance variables from IntentReceiver,
+ * which needs to be initiated from the constructor,
+ * with either default, or user provided value.
+ */
+ private final Context mContext;
+ private final String[] mIntentStrings;
+
+ /** Non-final variables as there are setters available */
+ private Duration mIntentTimeout;
+ private IntentListener mIntentListener;
+
+ /**
+ * Private default constructor to avoid creation of Builder default
+ * instance directly as we need some instance variables to be initiated
+ * with user defined values.
+ */
+ private Builder() {
+ mContext = null;
+ mIntentStrings = null;
+ }
+
+ /**
+ * Creates a Builder instance with following required params
+ *
+ * @param context Context
+ * @param intentStrings Array of intents to filter and register
+ */
+ public Builder(@NonNull Context context, String... intentStrings) {
+ mContext = context;
+ mIntentStrings = requireNonNull(intentStrings,
+ "IntentReceiver.Builder(): Intent string cannot be null");
+
+ if (mIntentStrings.length == 0) {
+ throw new RuntimeException("IntentReceiver.Builder(): No intents to register");
+ }
+
+ /* Default values for remaining vars */
+ mIntentTimeout = Duration.ofSeconds(10);
+ mIntentListener = null;
+ }
+
+ public Builder setIntentListener(IntentListener intentListener) {
+ mIntentListener = intentListener;
+ return this;
+ }
+
+ public Builder setIntentTimeout(Duration intentTimeout) {
+ mIntentTimeout = intentTimeout;
+ return this;
+ }
+
+ /**
+ * Builds and returns the IntentReceiver object with all the passed,
+ * and default params supplied to Builder().
+ */
+ public IntentReceiver build() {
+ return new IntentReceiver(this);
+ }
+ }
+
+ /**
+ * Verifies if the intent is received in order
+ *
+ * @param matchers Matchers
+ */
+ public void verifyReceivedOrdered(Matcher<Intent>... matchers) {
+ mInOrder.verify(mReceiver, timeout(mIntentTimeout.toMillis()))
+ .onReceive(any(Context.class), MockitoHamcrest.argThat(AllOf.allOf(matchers)));
+ }
+
+ /**
+ * Verifies if requested number of intents are received (unordered)
+ *
+ * @param num Number of intents
+ * @param matchers Matchers
+ */
+ public void verifyReceived(int num, Matcher<Intent>... matchers) {
+ verify(mReceiver, timeout(mIntentTimeout.toMillis()).times(num))
+ .onReceive(any(Context.class), MockitoHamcrest.argThat(AllOf.allOf(matchers)));
+ }
+
+ /**
+ * Verifies if the intent is received (unordered)
+ *
+ * @param matchers Matchers
+ */
+ public void verifyReceived(Matcher<Intent>... matchers) {
+ verifyReceived(1, matchers);
+ }
+
+ /**
+ * This function will make sure that the instance is properly cleared
+ * based on the registered actions.
+ * Note: This function MUST be called before returning from the caller function,
+ * as this either unregisters the latest registered actions, or free resources.
+ */
+ public void close() {
+ Log.d(TAG, "close(): " + mDqIntentFilter.size());
+
+ /* More than 1 IntentFilters are present */
+ if(mDqIntentFilter.size() > 1) {
+ /*
+ * It represents there are IntentFilters present to be rolled back.
+ * So, unregister and roll back to previous IntentFilter.
+ */
+ unregisterRecentAllIntentActions();
+ }
+ else {
+ /*
+ * It represents that this close() is called in the scope of creation of
+ * the object, and hence there is only 1 IntentFilter which is present.
+ * So, we can safely close this instance.
+ */
+ verifyNoMoreInteractions();
+ unregisterReceiver();
+ }
+ }
+
+ /**
+ * Registers the new actions passed as argument.
+ * 1. Unregister the receiver, and in turn old IntentFilter.
+ * 2. Creates a new IntentFilter from the String[], and treat that as latest.
+ * 3. Registers the new IntentFilter with the receiver to the current context.
+ */
+ public void registerIntentActions(String... intentStrings) {
+ IntentFilter intentFilter = prepareIntentFilter(intentStrings);
+
+ unregisterReceiver();
+ /* Pushes the new intentFilter to top to make it the latest registered */
+ mDqIntentFilter.addFirst(intentFilter);
+ registerReceiver();
+ }
+
+ /**
+ * Helper function to register intent actions, and get the IntentReceiver
+ * instance.
+ *
+ * @param parentIntentReceiver IntentReceiver instance from the parent test caller
+ * This should be `null` if there is no parent IntentReceiver instance.
+ * @param targetContext Context instance
+ * @param intentStrings Intent actions string array
+ *
+ * This should be used to register new intent actions in a testStep
+ * function always.
+ */
+ public static IntentReceiver updateNewIntentActionsInParentReceiver(
+ IntentReceiver parentIntentReceiver, Context targetContext, String... intentStrings) {
+ /*
+ * If parentIntentReceiver is NULL, it indicates that the caller
+ * is a fresh test/testStep and a new IntentReceiver will be returned.
+ * else, update the intent actions and return the same instance.
+ */
+ // Create a new instance for the current test/testStep function.
+ if(parentIntentReceiver == null)
+ return new IntentReceiver.Builder(targetContext, intentStrings)
+ .build();
+
+ /* Update the intent actions in the parent IntentReceiver instance */
+ parentIntentReceiver.registerIntentActions(intentStrings);
+ return parentIntentReceiver;
+ }
+
+ /** Helper functions are added below, usually private */
+
+ /** Registers the listener for the received intents, and perform a custom logic as required */
+ private void setupListener() {
+ doAnswer(
+ inv -> {
+ Log.d(
+ TAG,
+ "onReceive(): intent=" +
+ Arrays.toString(inv.getArguments()));
+
+ if (mIntentListener == null) return null;
+
+ Intent intent = inv.getArgument(1);
+
+ /* Custom `onReceive` will be provided by the caller */
+ mIntentListener.onReceive(intent);
+ return null;
+ })
+ .when(mReceiver)
+ .onReceive(any(), any());
+ }
+
+ private IntentFilter prepareIntentFilter(String... intentStrings) {
+ IntentFilter intentFilter = new IntentFilter();
+ for (String intentString : intentStrings) {
+ intentFilter.addAction(intentString);
+ }
+
+ return intentFilter;
+ }
+
+ /**
+ * Registers the latest intent filter which is at the deque.peekFirst()
+ * Note: The mDqIntentFilter must not be empty here.
+ */
+ private void registerReceiver() {
+ Log.d(TAG, "registerReceiver(): Registering for intents: " +
+ getActionsFromIntentFilter(mDqIntentFilter.peekFirst()));
+
+ /* ArrayDeque should not be empty at all while registering a receiver */
+ assertThat(mDqIntentFilter.isEmpty()).isFalse();
+ mContext.registerReceiver(mReceiver,
+ (IntentFilter)mDqIntentFilter.peekFirst());
+ }
+
+ /**
+ * Unregisters the receiver from the list of active receivers.
+ * Also, we can now re-use the same receiver, or register a new
+ * receiver with the same or different intent filter, the old
+ * registration is no longer valid.
+ * Source: Intents and intent filters (Android Developers)
+ */
+ private void unregisterReceiver() {
+ Log.d(TAG, "unregisterReceiver()");
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ /** Verifies that no more intents are received */
+ private void verifyNoMoreInteractions() {
+ Log.d(TAG, "verifyNoMoreInteractions()");
+ Mockito.verifyNoMoreInteractions(mReceiver);
+ }
+
+ /**
+ * Registers the new actions passed as argument.
+ * 1. Unregister the receiver, and in turn new IntentFilter.
+ * 2. Pops the new IntentFilter to roll-back to the old one.
+ * 3. Registers the old IntentFilter with the receiver to the current context.
+ */
+ private void unregisterRecentAllIntentActions() {
+ assertThat(mDqIntentFilter.isEmpty()).isFalse();
+
+ unregisterReceiver();
+ /* Restores the previous intent filter, and discard the latest */
+ mDqIntentFilter.removeFirst();
+ registerReceiver();
+ }
+
+ /**
+ * Helper function to get the actions from the IntentFilter
+ *
+ * @param intentFilter IntentFilter instance
+ *
+ * This is a helper function to get the actions from the IntentFilter,
+ * and return as a String.
+ */
+ private String getActionsFromIntentFilter(
+ IntentFilter intentFilter) {
+ Iterator<String> iterator = intentFilter.actionsIterator();
+ StringBuilder allIntentActions = new StringBuilder();
+ while (iterator.hasNext()) {
+ allIntentActions.append(iterator.next() + ", ");
+ }
+
+ return allIntentActions.toString();
+ }
+} \ No newline at end of file
diff --git a/system/bta/Android.bp b/system/bta/Android.bp
index 63bd5f4e53..57fd56b039 100644
--- a/system/bta/Android.bp
+++ b/system/bta/Android.bp
@@ -1063,6 +1063,95 @@ cc_test {
}
cc_test {
+ name: "bluetooth_ras_test",
+ test_suites: ["general-tests"],
+ defaults: [
+ "fluoride_bta_defaults",
+ "mts_defaults",
+ ],
+ host_supported: true,
+ isolated: false,
+ include_dirs: [
+ "packages/modules/Bluetooth/system",
+ "packages/modules/Bluetooth/system/bta/include",
+ "packages/modules/Bluetooth/system/bta/test/common",
+ "packages/modules/Bluetooth/system/stack/include",
+ ],
+ srcs: [
+ ":TestCommonMockFunctions",
+ ":TestMockBtaGatt",
+ ":TestMockMainShim",
+ ":TestMockMainShimEntry",
+ ":TestMockStackBtm",
+ ":TestMockStackBtmInterface",
+ ":TestMockStackBtmIso",
+ ":TestMockStackGatt",
+ ":TestMockStackL2cap",
+ ":TestStubOsi",
+ "gatt/database.cc",
+ "gatt/database_builder.cc",
+ "ras/ras_utils.cc",
+ "ras/ras_utils_test.cc",
+ "test/common/bta_gatt_queue_mock.cc",
+ "test/common/btif_storage_mock.cc",
+ "test/common/mock_device_groups.cc",
+ ],
+ shared_libs: [
+ "libaconfig_storage_read_api_cc",
+ "libbase",
+ "libcrypto",
+ "libcutils",
+ "libhidlbase",
+ "liblog",
+ ],
+ static_libs: [
+ "bluetooth_flags_c_lib_for_test",
+ "libbluetooth-types",
+ "libbluetooth_crypto_toolbox",
+ "libbluetooth_gd",
+ "libbluetooth_log",
+ "libbt-audio-hal-interface",
+ "libbt-common",
+ "libbt-platform-protos-lite",
+ "libchrome",
+ "libcom.android.sysprop.bluetooth.wrapped",
+ "libevent",
+ "libflagtest",
+ "libflatbuffers-cpp",
+ "libgmock",
+ "libgtest",
+ "liblc3",
+ "libosi",
+ "server_configurable_flags",
+ ],
+ target: {
+ android: {
+ shared_libs: [
+ "libbinder_ndk",
+ ],
+ static_libs: [
+ "libPlatformProperties",
+ ],
+ },
+ host: {
+ static_libs: [
+ "libbinder_ndk",
+ ],
+ },
+ },
+ sanitize: {
+ cfi: true,
+ scs: true,
+ address: true,
+ all_undefined: true,
+ integer_overflow: true,
+ diag: {
+ undefined: true,
+ },
+ },
+}
+
+cc_test {
name: "bluetooth_test_broadcaster_state_machine",
test_suites: ["general-tests"],
defaults: [
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index c5901065d5..6dbb4ace85 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -6160,6 +6160,20 @@ public:
}
break;
}
+ case GroupStreamStatus::RELEASING_AUTONOMOUS:
+ /* Remote device releases all the ASEs autonomusly. This should not happen and not sure what
+ * is the remote device intention. If remote wants stop the stream then MCS shall be used to
+ * stop the stream in a proper way. For a phone call, GTBS shall be used. For now we assume
+ * this device has does not want to be used for streaming and mark it as Inactive.
+ */
+ log::warn("Group {} is doing autonomous release, make it inactive", group_id);
+ if (group) {
+ group->PrintDebugState();
+ groupSetAndNotifyInactive();
+ }
+ audio_sender_state_ = AudioState::IDLE;
+ audio_receiver_state_ = AudioState::IDLE;
+ break;
case GroupStreamStatus::RELEASING:
case GroupStreamStatus::SUSPENDING:
if (active_group_id_ != bluetooth::groups::kGroupUnknown &&
@@ -6172,9 +6186,12 @@ public:
* it means that it is some internal state machine error. This is very unlikely and
* for now just Inactivate the group.
*/
- log::error("Internal state machine error");
+ log::error("Internal state machine error for group {}", group_id);
group->PrintDebugState();
groupSetAndNotifyInactive();
+ audio_sender_state_ = AudioState::IDLE;
+ audio_receiver_state_ = AudioState::IDLE;
+ return;
}
if (is_active_group_operation) {
diff --git a/system/bta/le_audio/le_audio_client_test.cc b/system/bta/le_audio/le_audio_client_test.cc
index 3e4c50aff3..693494f73c 100644
--- a/system/bta/le_audio/le_audio_client_test.cc
+++ b/system/bta/le_audio/le_audio_client_test.cc
@@ -6817,11 +6817,18 @@ TEST_F(UnicastTest, SpeakerStreamingAutonomousRelease) {
Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_);
Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_);
+ Mock::VerifyAndClearExpectations(mock_le_audio_sink_hal_client_);
SyncOnMainLoop();
// Verify Data transfer on one audio source cis
TestAudioDataTransfer(group_id, 1 /* cis_count_out */, 0 /* cis_count_in */, 1920);
+ EXPECT_CALL(mock_audio_hal_client_callbacks_, OnGroupStatus(group_id, GroupStatus::INACTIVE))
+ .Times(1);
+ EXPECT_CALL(mock_audio_hal_client_callbacks_,
+ OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE))
+ .Times(1);
+
// Inject the IDLE state as if an autonomous release happened
ASSERT_NE(0lu, streaming_groups.count(group_id));
auto group = streaming_groups.at(group_id);
@@ -6835,9 +6842,14 @@ TEST_F(UnicastTest, SpeakerStreamingAutonomousRelease) {
InjectCisDisconnected(group_id, ase.cis_conn_hdl);
}
}
-
// Verify no Data transfer after the autonomous release
TestAudioDataTransfer(group_id, 0 /* cis_count_out */, 0 /* cis_count_in */, 1920);
+
+ // Inject Releasing
+ state_machine_callbacks_->StatusReportCb(group->group_id_,
+ GroupStreamStatus::RELEASING_AUTONOMOUS);
+ SyncOnMainLoop();
+ Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_);
}
TEST_F(UnicastTest, TwoEarbudsStreaming) {
@@ -12121,6 +12133,16 @@ TEST_F(UnicastTest, GroupStreamStatus) {
EXPECT_CALL(mock_audio_hal_client_callbacks_,
OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE))
.Times(1);
+ state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::RELEASING_AUTONOMOUS);
+
+ EXPECT_CALL(mock_audio_hal_client_callbacks_,
+ OnGroupStreamStatus(group_id, GroupStreamStatus::STREAMING))
+ .Times(1);
+ state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::STREAMING);
+
+ EXPECT_CALL(mock_audio_hal_client_callbacks_,
+ OnGroupStreamStatus(group_id, GroupStreamStatus::IDLE))
+ .Times(1);
state_machine_callbacks_->StatusReportCb(group_id, GroupStreamStatus::SUSPENDING);
EXPECT_CALL(mock_audio_hal_client_callbacks_,
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
index dd1c535be1..8d8ca50ab0 100644
--- a/system/bta/le_audio/state_machine.cc
+++ b/system/bta/le_audio/state_machine.cc
@@ -3045,7 +3045,7 @@ private:
log::info("Group {} is doing autonomous release", group->group_id_);
SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
state_machine_callbacks_->StatusReportCb(group->group_id_,
- GroupStreamStatus::RELEASING);
+ GroupStreamStatus::RELEASING_AUTONOMOUS);
}
}
diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc
index 7fc2985b28..682bb9c722 100644
--- a/system/bta/le_audio/state_machine_test.cc
+++ b/system/bta/le_audio/state_machine_test.cc
@@ -4147,9 +4147,13 @@ TEST_F(StateMachineTest, testAutonomousReleaseMultiple) {
// Validate GroupStreamStatus
EXPECT_CALL(mock_callbacks_,
- StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::RELEASING))
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::RELEASING_AUTONOMOUS))
.Times(1);
EXPECT_CALL(mock_callbacks_,
+ StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::RELEASING))
+ .Times(0);
+ EXPECT_CALL(mock_callbacks_,
StatusReportCb(leaudio_group_id, bluetooth::le_audio::GroupStreamStatus::IDLE))
.Times(1);
EXPECT_CALL(mock_callbacks_,
diff --git a/system/bta/ras/ras_utils_test.cc b/system/bta/ras/ras_utils_test.cc
new file mode 100644
index 0000000000..7acc658265
--- /dev/null
+++ b/system/bta/ras/ras_utils_test.cc
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "bta/include/bta_ras_api.h"
+#include "bta/ras/ras_types.h"
+
+class RasUtilsTest : public ::testing::Test {};
+
+TEST(RasUtilsTest, GetUuidName) {
+ // Test known UUIDs
+ EXPECT_EQ(ras::uuid::getUuidName(bluetooth::Uuid::From16Bit(ras::uuid::kRangingService16Bit)),
+ "Ranging Service");
+ EXPECT_EQ(ras::uuid::getUuidName(
+ bluetooth::Uuid::From16Bit(ras::uuid::kRasFeaturesCharacteristic16bit)),
+ "RAS Features");
+ EXPECT_EQ(ras::uuid::getUuidName(bluetooth::Uuid::From16Bit(
+ ras::uuid::kRasRealTimeRangingDataCharacteristic16bit)),
+ "Real-time Ranging Data");
+ EXPECT_EQ(ras::uuid::getUuidName(
+ bluetooth::Uuid::From16Bit(ras::uuid::kRasOnDemandDataCharacteristic16bit)),
+ "On-demand Ranging Data");
+ EXPECT_EQ(ras::uuid::getUuidName(
+ bluetooth::Uuid::From16Bit(ras::uuid::kRasControlPointCharacteristic16bit)),
+ "RAS Control Point (RAS-CP)");
+ EXPECT_EQ(ras::uuid::getUuidName(
+ bluetooth::Uuid::From16Bit(ras::uuid::kRasRangingDataReadyCharacteristic16bit)),
+ "Ranging Data Ready");
+ EXPECT_EQ(ras::uuid::getUuidName(bluetooth::Uuid::From16Bit(
+ ras::uuid::kRasRangingDataOverWrittenCharacteristic16bit)),
+ "Ranging Data Overwritten");
+ EXPECT_EQ(ras::uuid::getUuidName(
+ bluetooth::Uuid::From16Bit(ras::uuid::kClientCharacteristicConfiguration16bit)),
+ "Client Characteristic Configuration");
+
+ // Test unknown UUID
+ EXPECT_EQ(ras::uuid::getUuidName(
+ bluetooth::Uuid::FromString("00001101-0000-1000-8000-00805F9B34FB")),
+ "Unknown UUID");
+}
+
+TEST(RasUtilsTest, ParseControlPointCommand) {
+ // Test successful parsing of valid commands
+ uint8_t valid_data_get_ranging_data[] = {0x00, 0x01, 0x02};
+ ras::ControlPointCommand command_get_ranging_data;
+ ASSERT_TRUE(ras::ParseControlPointCommand(&command_get_ranging_data, valid_data_get_ranging_data,
+ sizeof(valid_data_get_ranging_data)));
+ ASSERT_EQ(command_get_ranging_data.opcode_, ras::Opcode::GET_RANGING_DATA);
+ ASSERT_EQ(command_get_ranging_data.parameter_[0], 0x01);
+ ASSERT_EQ(command_get_ranging_data.parameter_[1], 0x02);
+
+ uint8_t valid_data_ack_ranging_data[] = {0x01, 0x03, 0x04};
+ ras::ControlPointCommand command_ack_ranging_data;
+ ASSERT_TRUE(ras::ParseControlPointCommand(&command_ack_ranging_data, valid_data_ack_ranging_data,
+ sizeof(valid_data_ack_ranging_data)));
+ ASSERT_EQ(command_ack_ranging_data.opcode_, ras::Opcode::ACK_RANGING_DATA);
+ ASSERT_EQ(command_ack_ranging_data.parameter_[0], 0x03);
+ ASSERT_EQ(command_ack_ranging_data.parameter_[1], 0x04);
+
+ uint8_t valid_data_retrieve_lost_ranging_data_segments[] = {0x02, 0x05, 0x06, 0x07, 0x08};
+ ras::ControlPointCommand command_retrieve_lost_ranging_data_segments;
+ ASSERT_TRUE(
+ ras::ParseControlPointCommand(&command_retrieve_lost_ranging_data_segments,
+ valid_data_retrieve_lost_ranging_data_segments,
+ sizeof(valid_data_retrieve_lost_ranging_data_segments)));
+ ASSERT_EQ(command_retrieve_lost_ranging_data_segments.opcode_,
+ ras::Opcode::RETRIEVE_LOST_RANGING_DATA_SEGMENTS);
+ ASSERT_EQ(command_retrieve_lost_ranging_data_segments.parameter_[0], 0x05);
+ ASSERT_EQ(command_retrieve_lost_ranging_data_segments.parameter_[1], 0x06);
+ ASSERT_EQ(command_retrieve_lost_ranging_data_segments.parameter_[2], 0x07);
+ ASSERT_EQ(command_retrieve_lost_ranging_data_segments.parameter_[3], 0x08);
+
+ uint8_t valid_data_abort_operation[] = {0x03};
+ ras::ControlPointCommand command_abort_operation;
+ ASSERT_TRUE(ras::ParseControlPointCommand(&command_abort_operation, valid_data_abort_operation,
+ sizeof(valid_data_abort_operation)));
+ ASSERT_EQ(command_abort_operation.opcode_, ras::Opcode::ABORT_OPERATION);
+
+ uint8_t valid_data_filter[] = {0x04, 0x09, 0x0A};
+ ras::ControlPointCommand command_filter;
+ ASSERT_TRUE(ras::ParseControlPointCommand(&command_filter, valid_data_filter,
+ sizeof(valid_data_filter)));
+ ASSERT_EQ(command_filter.opcode_, ras::Opcode::FILTER);
+ ASSERT_EQ(command_filter.parameter_[0], 0x09);
+ ASSERT_EQ(command_filter.parameter_[1], 0x0A);
+
+ // Test failed parsing of invalid commands
+ uint8_t invalid_data_short_get_ranging_data[] = {0x00, 0x01};
+ ras::ControlPointCommand command_invalid_short_get_ranging_data;
+ ASSERT_FALSE(ras::ParseControlPointCommand(&command_invalid_short_get_ranging_data,
+ invalid_data_short_get_ranging_data,
+ sizeof(invalid_data_short_get_ranging_data)));
+
+ uint8_t invalid_data_long_get_ranging_data[] = {0x00, 0x01, 0x02, 0x03};
+ ras::ControlPointCommand command_invalid_long_get_ranging_data;
+ ASSERT_FALSE(ras::ParseControlPointCommand(&command_invalid_long_get_ranging_data,
+ invalid_data_long_get_ranging_data,
+ sizeof(invalid_data_long_get_ranging_data)));
+
+ uint8_t invalid_data_unknown_opcode[] = {0x05, 0x01, 0x02};
+ ras::ControlPointCommand command_invalid_unknown_opcode;
+ ASSERT_FALSE(ras::ParseControlPointCommand(&command_invalid_unknown_opcode,
+ invalid_data_unknown_opcode,
+ sizeof(invalid_data_unknown_opcode)));
+}
+
+TEST(RasUtilsTest, GetOpcodeText) {
+ // Test known opcodes
+ EXPECT_EQ(ras::GetOpcodeText(ras::Opcode::GET_RANGING_DATA), "GET_RANGING_DATA");
+ EXPECT_EQ(ras::GetOpcodeText(ras::Opcode::ACK_RANGING_DATA), "ACK_RANGING_DATA");
+ EXPECT_EQ(ras::GetOpcodeText(ras::Opcode::RETRIEVE_LOST_RANGING_DATA_SEGMENTS),
+ "RETRIEVE_LOST_RANGING_DATA_SEGMENTS");
+ EXPECT_EQ(ras::GetOpcodeText(ras::Opcode::ABORT_OPERATION), "ABORT_OPERATION");
+ EXPECT_EQ(ras::GetOpcodeText(ras::Opcode::FILTER), "FILTER");
+
+ // Test unknown opcode (casting an invalid value to Opcode)
+ EXPECT_EQ(ras::GetOpcodeText(static_cast<ras::Opcode>(0x05)), "Unknown Opcode");
+}
+
+TEST(RasUtilsTest, GetResponseOpcodeValueText) {
+ // Test known response code values
+ EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::RESERVED_FOR_FUTURE_USE),
+ "RESERVED_FOR_FUTURE_USE");
+ EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::SUCCESS), "SUCCESS");
+ EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::OP_CODE_NOT_SUPPORTED),
+ "OP_CODE_NOT_SUPPORTED");
+ EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::INVALID_PARAMETER),
+ "INVALID_PARAMETER");
+ EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::PERSISTED), "PERSISTED");
+ EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::ABORT_UNSUCCESSFUL),
+ "ABORT_UNSUCCESSFUL");
+ EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::PROCEDURE_NOT_COMPLETED),
+ "PROCEDURE_NOT_COMPLETED");
+ EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::SERVER_BUSY), "SERVER_BUSY");
+ EXPECT_EQ(ras::GetResponseOpcodeValueText(ras::ResponseCodeValue::NO_RECORDS_FOUND),
+ "NO_RECORDS_FOUND");
+
+ // Test unknown response code value (casting an invalid value to ResponseCodeValue)
+ EXPECT_EQ(ras::GetResponseOpcodeValueText(static_cast<ras::ResponseCodeValue>(0x09)),
+ "Reserved for Future Use");
+}
+
+TEST(RasUtilsTest, IsRangingServiceCharacteristic) {
+ // Test true cases for Ranging Service characteristics
+ EXPECT_TRUE(ras::IsRangingServiceCharacteristic(
+ bluetooth::Uuid::From16Bit(ras::uuid::kRangingService16Bit)));
+ EXPECT_TRUE(ras::IsRangingServiceCharacteristic(
+ bluetooth::Uuid::From16Bit(ras::uuid::kRasFeaturesCharacteristic16bit)));
+ EXPECT_TRUE(ras::IsRangingServiceCharacteristic(
+ bluetooth::Uuid::From16Bit(ras::uuid::kRasRealTimeRangingDataCharacteristic16bit)));
+ EXPECT_TRUE(ras::IsRangingServiceCharacteristic(
+ bluetooth::Uuid::From16Bit(ras::uuid::kRasOnDemandDataCharacteristic16bit)));
+ EXPECT_TRUE(ras::IsRangingServiceCharacteristic(
+ bluetooth::Uuid::From16Bit(ras::uuid::kRasControlPointCharacteristic16bit)));
+ EXPECT_TRUE(ras::IsRangingServiceCharacteristic(
+ bluetooth::Uuid::From16Bit(ras::uuid::kRasRangingDataReadyCharacteristic16bit)));
+ EXPECT_TRUE(ras::IsRangingServiceCharacteristic(
+ bluetooth::Uuid::From16Bit(ras::uuid::kRasRangingDataOverWrittenCharacteristic16bit)));
+
+ // Test false cases for non-Ranging Service characteristics
+ EXPECT_FALSE(ras::IsRangingServiceCharacteristic(
+ bluetooth::Uuid::From16Bit(ras::uuid::kClientCharacteristicConfiguration16bit)));
+ EXPECT_FALSE(ras::IsRangingServiceCharacteristic(
+ bluetooth::Uuid::FromString("00001101-0000-1000-8000-00805F9B34FB"))); // Random UUID
+}
diff --git a/system/gd/rust/topshim/le_audio/le_audio_shim.cc b/system/gd/rust/topshim/le_audio/le_audio_shim.cc
index 77d7a5c60d..53a0f7d98f 100644
--- a/system/gd/rust/topshim/le_audio/le_audio_shim.cc
+++ b/system/gd/rust/topshim/le_audio/le_audio_shim.cc
@@ -162,6 +162,8 @@ static BtLeAudioGroupStreamStatus to_rust_btle_audio_group_stream_status(
return BtLeAudioGroupStreamStatus::Streaming;
case le_audio::GroupStreamStatus::RELEASING:
return BtLeAudioGroupStreamStatus::Releasing;
+ case le_audio::GroupStreamStatus::RELEASING_AUTONOMOUS:
+ return BtLeAudioGroupStreamStatus::ReleasingAutonomous;
case le_audio::GroupStreamStatus::SUSPENDING:
return BtLeAudioGroupStreamStatus::Suspending;
case le_audio::GroupStreamStatus::SUSPENDED:
diff --git a/system/gd/rust/topshim/src/profiles/le_audio.rs b/system/gd/rust/topshim/src/profiles/le_audio.rs
index 266f24f7fb..651ad82ecd 100644
--- a/system/gd/rust/topshim/src/profiles/le_audio.rs
+++ b/system/gd/rust/topshim/src/profiles/le_audio.rs
@@ -109,6 +109,7 @@ pub mod ffi {
Idle = 0,
Streaming,
Releasing,
+ ReleasingAutonomous,
Suspending,
Suspended,
ConfiguredAutonomous,
@@ -413,11 +414,12 @@ impl From<BtLeAudioGroupStreamStatus> for i32 {
BtLeAudioGroupStreamStatus::Idle => 0,
BtLeAudioGroupStreamStatus::Streaming => 1,
BtLeAudioGroupStreamStatus::Releasing => 2,
- BtLeAudioGroupStreamStatus::Suspending => 3,
- BtLeAudioGroupStreamStatus::Suspended => 4,
- BtLeAudioGroupStreamStatus::ConfiguredAutonomous => 5,
- BtLeAudioGroupStreamStatus::ConfiguredByUser => 6,
- BtLeAudioGroupStreamStatus::Destroyed => 7,
+ BtLeAudioGroupStreamStatus::ReleasingAutonomous => 3,
+ BtLeAudioGroupStreamStatus::Suspending => 4,
+ BtLeAudioGroupStreamStatus::Suspended => 5,
+ BtLeAudioGroupStreamStatus::ConfiguredAutonomous => 6,
+ BtLeAudioGroupStreamStatus::ConfiguredByUser => 7,
+ BtLeAudioGroupStreamStatus::Destroyed => 8,
_ => panic!("Invalid value {:?} to BtLeAudioGroupStreamStatus", value),
}
}
@@ -429,11 +431,12 @@ impl From<i32> for BtLeAudioGroupStreamStatus {
0 => BtLeAudioGroupStreamStatus::Idle,
1 => BtLeAudioGroupStreamStatus::Streaming,
2 => BtLeAudioGroupStreamStatus::Releasing,
- 3 => BtLeAudioGroupStreamStatus::Suspending,
- 4 => BtLeAudioGroupStreamStatus::Suspended,
- 5 => BtLeAudioGroupStreamStatus::ConfiguredAutonomous,
- 6 => BtLeAudioGroupStreamStatus::ConfiguredByUser,
- 7 => BtLeAudioGroupStreamStatus::Destroyed,
+ 3 => BtLeAudioGroupStreamStatus::ReleasingAutonomous,
+ 4 => BtLeAudioGroupStreamStatus::Suspending,
+ 5 => BtLeAudioGroupStreamStatus::Suspended,
+ 6 => BtLeAudioGroupStreamStatus::ConfiguredAutonomous,
+ 7 => BtLeAudioGroupStreamStatus::ConfiguredByUser,
+ 8 => BtLeAudioGroupStreamStatus::Destroyed,
_ => panic!("Invalid value {} to BtLeAudioGroupStreamStatus", value),
}
}
diff --git a/system/include/hardware/bt_le_audio.h b/system/include/hardware/bt_le_audio.h
index 91ce17c388..94c3762596 100644
--- a/system/include/hardware/bt_le_audio.h
+++ b/system/include/hardware/bt_le_audio.h
@@ -70,6 +70,7 @@ enum class GroupStreamStatus {
IDLE = 0,
STREAMING,
RELEASING,
+ RELEASING_AUTONOMOUS,
SUSPENDING,
SUSPENDED,
CONFIGURED_AUTONOMOUS,