Merge "Update name threadnetwork.country_code in comments" into main
diff --git a/service-t/jni/com_android_server_net_NetworkStatsService.cpp b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
index 48ac993..c999398 100644
--- a/service-t/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
@@ -52,8 +52,14 @@
return nullptr;
}
+ // Find the constructor.
+ jmethodID constructorID = env->GetMethodID(gEntryClass, "<init>", "()V");
+ if (constructorID == nullptr) {
+ return nullptr;
+ }
+
// Create a new instance of the Java class
- jobject result = env->AllocObject(gEntryClass);
+ jobject result = env->NewObject(gEntryClass, constructorID);
if (result == nullptr) {
return nullptr;
}
diff --git a/thread/scripts/make-pretty.sh b/thread/scripts/make-pretty.sh
index e4bd459..c176bfa 100755
--- a/thread/scripts/make-pretty.sh
+++ b/thread/scripts/make-pretty.sh
@@ -3,5 +3,7 @@
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
GOOGLE_JAVA_FORMAT=$SCRIPT_DIR/../../../../../prebuilts/tools/common/google-java-format/google-java-format
+ANDROID_BP_FORMAT=$SCRIPT_DIR/../../../../../prebuilts/build-tools/linux-x86/bin/bpfmt
$GOOGLE_JAVA_FORMAT --aosp -i $(find $SCRIPT_DIR/../ -name "*.java")
+$ANDROID_BP_FORMAT -w $(find $SCRIPT_DIR/../ -name "*.bp")
diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index 3cf31e5..2f38bfd 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -45,7 +45,7 @@
libs: [
"android.test.base",
"android.test.runner",
- "framework-connectivity-module-api-stubs-including-flagged"
+ "framework-connectivity-module-api-stubs-including-flagged",
],
// Test coverage system runs on different devices. Need to
// compile for all architectures.
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 7a129dc..aab4b2e 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -39,6 +39,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
import static org.junit.Assume.assumeNotNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -66,6 +67,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.FunctionalUtils.ThrowingRunnable;
import org.junit.After;
import org.junit.Before;
@@ -95,50 +97,707 @@
private static final int NETWORK_CALLBACK_TIMEOUT_MILLIS = 10 * 1000;
private static final int CALLBACK_TIMEOUT_MILLIS = 1_000;
private static final int ENABLED_TIMEOUT_MILLIS = 2_000;
- private static final String PERMISSION_THREAD_NETWORK_PRIVILEGED =
+ private static final String THREAD_NETWORK_PRIVILEGED =
"android.permission.THREAD_NETWORK_PRIVILEGED";
@Rule public DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
private ExecutorService mExecutor;
- private ThreadNetworkManager mManager;
+ private ThreadNetworkController mController;
private Set<String> mGrantedPermissions;
@Before
public void setUp() throws Exception {
- mExecutor = Executors.newSingleThreadExecutor();
- mManager = mContext.getSystemService(ThreadNetworkManager.class);
+
mGrantedPermissions = new HashSet<String>();
+ mExecutor = Executors.newSingleThreadExecutor();
+ ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
+ if (manager != null) {
+ mController = manager.getAllThreadNetworkControllers().get(0);
+ }
// TODO: we will also need it in tearDown(), it's better to have a Rule to skip
// tests if a feature is not available.
- assumeNotNull(mManager);
+ assumeNotNull(mController);
- for (ThreadNetworkController controller : getAllControllers()) {
- setEnabledAndWait(controller, true);
- }
+ setEnabledAndWait(mController, true);
}
@After
public void tearDown() throws Exception {
- if (mManager != null) {
- dropAllPermissions();
- leaveAndWait();
- }
- }
-
- private List<ThreadNetworkController> getAllControllers() {
- return mManager.getAllThreadNetworkControllers();
- }
-
- private void leaveAndWait() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
+ if (mController != null) {
+ grantPermissions(THREAD_NETWORK_PRIVILEGED);
CompletableFuture<Void> future = new CompletableFuture<>();
- leave(controller, future::complete);
+ mController.leave(mExecutor, future::complete);
future.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
}
+ dropAllPermissions();
+ }
+
+ @Test
+ public void getThreadVersion_returnsAtLeastThreadVersion1P3() {
+ assertThat(mController.getThreadVersion()).isAtLeast(THREAD_VERSION_1_3);
+ }
+
+ @Test
+ public void registerStateCallback_permissionsGranted_returnsCurrentStates() throws Exception {
+ CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+ StateCallback callback = deviceRole::complete;
+
+ try {
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ () -> mController.registerStateCallback(mExecutor, callback));
+
+ assertThat(deviceRole.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS))
+ .isEqualTo(DEVICE_ROLE_STOPPED);
+ } finally {
+ runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
+ }
+ }
+
+ @Test
+ public void registerStateCallback_returnsUpdatedEnabledStates() throws Exception {
+ CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
+ EnabledStateListener listener = new EnabledStateListener(mController);
+
+ try {
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> {
+ mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture1));
+ });
+ setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> {
+ mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture2));
+ });
+ setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+
+ listener.expectThreadEnabledState(STATE_ENABLED);
+ listener.expectThreadEnabledState(STATE_DISABLING);
+ listener.expectThreadEnabledState(STATE_DISABLED);
+ listener.expectThreadEnabledState(STATE_ENABLED);
+ } finally {
+ listener.unregisterStateCallback();
+ }
+ }
+
+ @Test
+ public void registerStateCallback_noPermissions_throwsSecurityException() throws Exception {
+ dropAllPermissions();
+
+ assertThrows(
+ SecurityException.class,
+ () -> mController.registerStateCallback(mExecutor, role -> {}));
+ }
+
+ @Test
+ public void registerStateCallback_alreadyRegistered_throwsIllegalArgumentException()
+ throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE);
+ CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+ StateCallback callback = role -> deviceRole.complete(role);
+
+ mController.registerStateCallback(mExecutor, callback);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.registerStateCallback(mExecutor, callback));
+ }
+
+ @Test
+ public void unregisterStateCallback_noPermissions_throwsSecurityException() throws Exception {
+ CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+ StateCallback callback = role -> deviceRole.complete(role);
+ runAsShell(
+ ACCESS_NETWORK_STATE, () -> mController.registerStateCallback(mExecutor, callback));
+
+ try {
+ dropAllPermissions();
+ assertThrows(
+ SecurityException.class, () -> mController.unregisterStateCallback(callback));
+ } finally {
+ runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
+ }
+ }
+
+ @Test
+ public void unregisterStateCallback_callbackRegistered_success() throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE);
+ CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+ StateCallback callback = role -> deviceRole.complete(role);
+
+ assertDoesNotThrow(() -> mController.registerStateCallback(mExecutor, callback));
+ mController.unregisterStateCallback(callback);
+ }
+
+ @Test
+ public void unregisterStateCallback_callbackNotRegistered_throwsIllegalArgumentException()
+ throws Exception {
+ CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+ StateCallback callback = role -> deviceRole.complete(role);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.unregisterStateCallback(callback));
+ }
+
+ @Test
+ public void unregisterStateCallback_alreadyUnregistered_throwsIllegalArgumentException()
+ throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE);
+ CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
+ StateCallback callback = deviceRole::complete;
+ mController.registerStateCallback(mExecutor, callback);
+ mController.unregisterStateCallback(callback);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.unregisterStateCallback(callback));
+ }
+
+ @Test
+ public void registerOperationalDatasetCallback_permissionsGranted_returnsCurrentStates()
+ throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+ CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
+ CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
+ var callback = newDatasetCallback(activeFuture, pendingFuture);
+
+ try {
+ mController.registerOperationalDatasetCallback(mExecutor, callback);
+
+ assertThat(activeFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
+ assertThat(pendingFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
+ } finally {
+ mController.unregisterOperationalDatasetCallback(callback);
+ }
+ }
+
+ @Test
+ public void registerOperationalDatasetCallback_noPermissions_throwsSecurityException()
+ throws Exception {
+ dropAllPermissions();
+ CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
+ CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
+ var callback = newDatasetCallback(activeFuture, pendingFuture);
+
+ assertThrows(
+ SecurityException.class,
+ () -> mController.registerOperationalDatasetCallback(mExecutor, callback));
+ }
+
+ @Test
+ public void unregisterOperationalDatasetCallback_callbackRegistered_success() throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+ CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
+ CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
+ var callback = newDatasetCallback(activeFuture, pendingFuture);
+ mController.registerOperationalDatasetCallback(mExecutor, callback);
+
+ assertDoesNotThrow(() -> mController.unregisterOperationalDatasetCallback(callback));
+ }
+
+ @Test
+ public void unregisterOperationalDatasetCallback_noPermissions_throwsSecurityException()
+ throws Exception {
+ CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
+ CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
+ var callback = newDatasetCallback(activeFuture, pendingFuture);
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.registerOperationalDatasetCallback(mExecutor, callback));
+
+ try {
+ dropAllPermissions();
+ assertThrows(
+ SecurityException.class,
+ () -> mController.unregisterOperationalDatasetCallback(callback));
+ } finally {
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.unregisterOperationalDatasetCallback(callback));
+ }
+ }
+
+ private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
+ CompletableFuture<V> future) {
+ return new OutcomeReceiver<V, ThreadNetworkException>() {
+ @Override
+ public void onResult(V result) {
+ future.complete(result);
+ }
+
+ @Override
+ public void onError(ThreadNetworkException e) {
+ future.completeExceptionally(e);
+ }
+ };
+ }
+
+ @Test
+ public void join_withPrivilegedPermission_success() throws Exception {
+ ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
+ CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
+ joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+
+ assertThat(isAttached(mController)).isTrue();
+ assertThat(getActiveOperationalDataset(mController)).isEqualTo(activeDataset);
+ }
+
+ @Test
+ public void join_withoutPrivilegedPermission_throwsSecurityException() throws Exception {
+ dropAllPermissions();
+ ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
+
+ assertThrows(
+ SecurityException.class, () -> mController.join(activeDataset, mExecutor, v -> {}));
+ }
+
+ @Test
+ public void join_threadDisabled_failsWithErrorThreadDisabled() throws Exception {
+ setEnabledAndWait(mController, false);
+ ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
+ CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
+
+ var thrown =
+ assertThrows(
+ ExecutionException.class,
+ () -> joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS));
+ var threadException = (ThreadNetworkException) thrown.getCause();
+ assertThat(threadException.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED);
+ }
+
+ @Test
+ public void join_concurrentRequests_firstOneIsAborted() throws Exception {
+ final byte[] KEY_1 = new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+ final byte[] KEY_2 = new byte[] {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+ ActiveOperationalDataset activeDataset1 =
+ new ActiveOperationalDataset.Builder(newRandomizedDataset("TestNet", mController))
+ .setNetworkKey(KEY_1)
+ .build();
+ ActiveOperationalDataset activeDataset2 =
+ new ActiveOperationalDataset.Builder(activeDataset1).setNetworkKey(KEY_2).build();
+ CompletableFuture<Void> joinFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> joinFuture2 = new CompletableFuture<>();
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> {
+ mController.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture1));
+ mController.join(activeDataset2, mExecutor, newOutcomeReceiver(joinFuture2));
+ });
+
+ var thrown =
+ assertThrows(
+ ExecutionException.class,
+ () -> joinFuture1.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS));
+ var threadException = (ThreadNetworkException) thrown.getCause();
+ assertThat(threadException.getErrorCode()).isEqualTo(ERROR_ABORTED);
+ joinFuture2.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+ assertThat(isAttached(mController)).isTrue();
+ assertThat(getActiveOperationalDataset(mController)).isEqualTo(activeDataset2);
+ }
+
+ @Test
+ public void leave_withPrivilegedPermission_success() throws Exception {
+ CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
+ joinRandomizedDatasetAndWait(mController);
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.leave(mExecutor, newOutcomeReceiver(leaveFuture)));
+ leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+
+ assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+ }
+
+ @Test
+ public void leave_withoutPrivilegedPermission_throwsSecurityException() {
+ dropAllPermissions();
+
+ assertThrows(SecurityException.class, () -> mController.leave(mExecutor, v -> {}));
+ }
+
+ @Test
+ public void leave_threadDisabled_success() throws Exception {
+ setEnabledAndWait(mController, false);
+ CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
+
+ leave(mController, newOutcomeReceiver(leaveFuture));
+ leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+
+ assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+ }
+
+ @Test
+ public void leave_concurrentRequests_bothSuccess() throws Exception {
+ CompletableFuture<Void> leaveFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> leaveFuture2 = new CompletableFuture<>();
+ joinRandomizedDatasetAndWait(mController);
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> {
+ mController.leave(mExecutor, newOutcomeReceiver(leaveFuture1));
+ mController.leave(mExecutor, newOutcomeReceiver(leaveFuture2));
+ });
+
+ leaveFuture1.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+ leaveFuture2.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
+ assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+ }
+
+ @Test
+ public void scheduleMigration_withPrivilegedPermission_newDatasetApplied() throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+ ActiveOperationalDataset activeDataset1 =
+ new ActiveOperationalDataset.Builder(newRandomizedDataset("TestNet", mController))
+ .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
+ .setExtendedPanId(new byte[] {1, 1, 1, 1, 1, 1, 1, 1})
+ .build();
+ ActiveOperationalDataset activeDataset2 =
+ new ActiveOperationalDataset.Builder(activeDataset1)
+ .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
+ .setNetworkName("ThreadNet2")
+ .build();
+ PendingOperationalDataset pendingDataset2 =
+ new PendingOperationalDataset(
+ activeDataset2,
+ OperationalDatasetTimestamp.fromInstant(Instant.now()),
+ Duration.ofSeconds(30));
+ CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+ CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
+ mController.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture));
+ joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+
+ mController.scheduleMigration(
+ pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture));
+ migrateFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+
+ CompletableFuture<Boolean> dataset2IsApplied = new CompletableFuture<>();
+ CompletableFuture<Boolean> pendingDatasetIsRemoved = new CompletableFuture<>();
+ OperationalDatasetCallback datasetCallback =
+ new OperationalDatasetCallback() {
+ @Override
+ public void onActiveOperationalDatasetChanged(
+ ActiveOperationalDataset activeDataset) {
+ if (activeDataset.equals(activeDataset2)) {
+ dataset2IsApplied.complete(true);
+ }
+ }
+
+ @Override
+ public void onPendingOperationalDatasetChanged(
+ PendingOperationalDataset pendingDataset) {
+ if (pendingDataset == null) {
+ pendingDatasetIsRemoved.complete(true);
+ }
+ }
+ };
+ mController.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
+ try {
+ assertThat(dataset2IsApplied.get(MIGRATION_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
+ assertThat(pendingDatasetIsRemoved.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
+ } finally {
+ mController.unregisterOperationalDatasetCallback(datasetCallback);
+ }
+ }
+
+ @Test
+ public void scheduleMigration_whenNotAttached_failWithPreconditionError() throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+ PendingOperationalDataset pendingDataset =
+ new PendingOperationalDataset(
+ newRandomizedDataset("TestNet", mController),
+ OperationalDatasetTimestamp.fromInstant(Instant.now()),
+ Duration.ofSeconds(30));
+ CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
+
+ mController.scheduleMigration(pendingDataset, mExecutor, newOutcomeReceiver(migrateFuture));
+
+ ThreadNetworkException thrown =
+ (ThreadNetworkException)
+ assertThrows(ExecutionException.class, migrateFuture::get).getCause();
+ assertThat(thrown.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
+ }
+
+ @Test
+ public void scheduleMigration_secondRequestHasSmallerTimestamp_rejectedByLeader()
+ throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+ final ActiveOperationalDataset activeDataset =
+ new ActiveOperationalDataset.Builder(newRandomizedDataset("testNet", mController))
+ .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
+ .build();
+ ActiveOperationalDataset activeDataset1 =
+ new ActiveOperationalDataset.Builder(activeDataset)
+ .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
+ .setNetworkName("testNet1")
+ .build();
+ PendingOperationalDataset pendingDataset1 =
+ new PendingOperationalDataset(
+ activeDataset1,
+ new OperationalDatasetTimestamp(100, 0, false),
+ Duration.ofSeconds(30));
+ ActiveOperationalDataset activeDataset2 =
+ new ActiveOperationalDataset.Builder(activeDataset)
+ .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false))
+ .setNetworkName("testNet2")
+ .build();
+ PendingOperationalDataset pendingDataset2 =
+ new PendingOperationalDataset(
+ activeDataset2,
+ new OperationalDatasetTimestamp(20, 0, false),
+ Duration.ofSeconds(30));
+ CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+ CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>();
+ mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
+ joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+
+ mController.scheduleMigration(
+ pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
+ migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+ mController.scheduleMigration(
+ pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
+
+ ThreadNetworkException thrown =
+ (ThreadNetworkException)
+ assertThrows(ExecutionException.class, migrateFuture2::get).getCause();
+ assertThat(thrown.getErrorCode()).isEqualTo(ERROR_REJECTED_BY_PEER);
+ }
+
+ @Test
+ public void scheduleMigration_secondRequestHasLargerTimestamp_newDatasetApplied()
+ throws Exception {
+ grantPermissions(ACCESS_NETWORK_STATE, THREAD_NETWORK_PRIVILEGED);
+ final ActiveOperationalDataset activeDataset =
+ new ActiveOperationalDataset.Builder(newRandomizedDataset("validName", mController))
+ .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
+ .build();
+ ActiveOperationalDataset activeDataset1 =
+ new ActiveOperationalDataset.Builder(activeDataset)
+ .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
+ .setNetworkName("testNet1")
+ .build();
+ PendingOperationalDataset pendingDataset1 =
+ new PendingOperationalDataset(
+ activeDataset1,
+ new OperationalDatasetTimestamp(100, 0, false),
+ Duration.ofSeconds(30));
+ ActiveOperationalDataset activeDataset2 =
+ new ActiveOperationalDataset.Builder(activeDataset)
+ .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false))
+ .setNetworkName("testNet2")
+ .build();
+ PendingOperationalDataset pendingDataset2 =
+ new PendingOperationalDataset(
+ activeDataset2,
+ new OperationalDatasetTimestamp(200, 0, false),
+ Duration.ofSeconds(30));
+ CompletableFuture<Void> joinFuture = new CompletableFuture<>();
+ CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>();
+ mController.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
+ joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
+
+ mController.scheduleMigration(
+ pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
+ migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+ mController.scheduleMigration(
+ pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
+ migrateFuture2.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+
+ CompletableFuture<Boolean> dataset2IsApplied = new CompletableFuture<>();
+ CompletableFuture<Boolean> pendingDatasetIsRemoved = new CompletableFuture<>();
+ OperationalDatasetCallback datasetCallback =
+ new OperationalDatasetCallback() {
+ @Override
+ public void onActiveOperationalDatasetChanged(
+ ActiveOperationalDataset activeDataset) {
+ if (activeDataset.equals(activeDataset2)) {
+ dataset2IsApplied.complete(true);
+ }
+ }
+
+ @Override
+ public void onPendingOperationalDatasetChanged(
+ PendingOperationalDataset pendingDataset) {
+ if (pendingDataset == null) {
+ pendingDatasetIsRemoved.complete(true);
+ }
+ }
+ };
+ mController.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
+ try {
+ assertThat(dataset2IsApplied.get(MIGRATION_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
+ assertThat(pendingDatasetIsRemoved.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
+ } finally {
+ mController.unregisterOperationalDatasetCallback(datasetCallback);
+ }
+ }
+
+ @Test
+ public void scheduleMigration_threadDisabled_failsWithErrorThreadDisabled() throws Exception {
+ ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", mController);
+ PendingOperationalDataset pendingDataset =
+ new PendingOperationalDataset(
+ activeDataset,
+ OperationalDatasetTimestamp.fromInstant(Instant.now()),
+ Duration.ofSeconds(30));
+ joinRandomizedDatasetAndWait(mController);
+ CompletableFuture<Void> migrationFuture = new CompletableFuture<>();
+
+ setEnabledAndWait(mController, false);
+
+ scheduleMigration(mController, pendingDataset, newOutcomeReceiver(migrationFuture));
+
+ ThreadNetworkException thrown =
+ (ThreadNetworkException)
+ assertThrows(ExecutionException.class, migrationFuture::get).getCause();
+ assertThat(thrown.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED);
+ }
+
+ @Test
+ public void createRandomizedDataset_wrongNetworkNameLength_throwsIllegalArgumentException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.createRandomizedDataset("", mExecutor, dataset -> {}));
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ mController.createRandomizedDataset(
+ "ANetNameIs17Bytes", mExecutor, dataset -> {}));
+ }
+
+ @Test
+ public void createRandomizedDataset_validNetworkName_success() throws Exception {
+ ActiveOperationalDataset dataset = newRandomizedDataset("validName", mController);
+
+ assertThat(dataset.getNetworkName()).isEqualTo("validName");
+ assertThat(dataset.getPanId()).isLessThan(0xffff);
+ assertThat(dataset.getChannelMask().size()).isAtLeast(1);
+ assertThat(dataset.getExtendedPanId()).hasLength(8);
+ assertThat(dataset.getNetworkKey()).hasLength(16);
+ assertThat(dataset.getPskc()).hasLength(16);
+ assertThat(dataset.getMeshLocalPrefix().getPrefixLength()).isEqualTo(64);
+ assertThat(dataset.getMeshLocalPrefix().getRawAddress()[0]).isEqualTo((byte) 0xfd);
+ }
+
+ @Test
+ public void setEnabled_permissionsGranted_succeeds() throws Exception {
+ CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture1)));
+ setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+ waitForEnabledState(mController, booleanToEnabledState(false));
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture2)));
+ setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+ waitForEnabledState(mController, booleanToEnabledState(true));
+ }
+
+ @Test
+ public void setEnabled_noPermissions_throwsSecurityException() throws Exception {
+ CompletableFuture<Void> setFuture = new CompletableFuture<>();
+ assertThrows(
+ SecurityException.class,
+ () -> mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture)));
+ }
+
+ @Test
+ public void setEnabled_disable_leavesThreadNetwork() throws Exception {
+ joinRandomizedDatasetAndWait(mController);
+ setEnabledAndWait(mController, false);
+ assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+ }
+
+ @Test
+ public void setEnabled_toggleAfterJoin_joinsThreadNetworkAgain() throws Exception {
+ joinRandomizedDatasetAndWait(mController);
+
+ setEnabledAndWait(mController, false);
+ assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+ setEnabledAndWait(mController, true);
+
+ runAsShell(ACCESS_NETWORK_STATE, () -> waitForAttachedState(mController));
+ }
+
+ @Test
+ public void setEnabled_enableFollowedByDisable_allSucceed() throws Exception {
+ joinRandomizedDatasetAndWait(mController);
+ CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
+ EnabledStateListener listener = new EnabledStateListener(mController);
+ listener.expectThreadEnabledState(STATE_ENABLED);
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> {
+ mController.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture1));
+ mController.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture2));
+ });
+ setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+ setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+
+ listener.expectThreadEnabledState(STATE_DISABLING);
+ listener.expectThreadEnabledState(STATE_DISABLED);
+ assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
+ // FIXME: this is not called when a exception is thrown after the creation of `listener`
+ listener.unregisterStateCallback();
+ }
+
+ // TODO (b/322437869): add test case to verify when Thread is in DISABLING state, any commands
+ // (join/leave/scheduleMigration/setEnabled) fail with ERROR_BUSY. This is not currently tested
+ // because DISABLING has very short lifecycle, it's not possible to guarantee the command can be
+ // sent before state changes to DISABLED.
+
+ @Test
+ public void threadNetworkCallback_deviceAttached_threadNetworkIsAvailable() throws Exception {
+ CompletableFuture<Network> networkFuture = new CompletableFuture<>();
+ ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ NetworkRequest networkRequest =
+ new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
+ .build();
+ ConnectivityManager.NetworkCallback networkCallback =
+ new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ networkFuture.complete(network);
+ }
+ };
+
+ joinRandomizedDatasetAndWait(mController);
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ () -> cm.registerNetworkCallback(networkRequest, networkCallback));
+
+ assertThat(isAttached(mController)).isTrue();
+ assertThat(networkFuture.get(NETWORK_CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNotNull();
}
private void grantPermissions(String... permissions) {
@@ -168,10 +827,14 @@
private static int getDeviceRole(ThreadNetworkController controller) throws Exception {
CompletableFuture<Integer> future = new CompletableFuture<>();
StateCallback callback = future::complete;
- controller.registerStateCallback(directExecutor(), callback);
- int role = future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
- controller.unregisterStateCallback(callback);
- return role;
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ () -> controller.registerStateCallback(directExecutor(), callback));
+ try {
+ return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+ } finally {
+ runAsShell(ACCESS_NETWORK_STATE, () -> controller.unregisterStateCallback(callback));
+ }
}
private static int waitForAttachedState(ThreadNetworkController controller) throws Exception {
@@ -222,8 +885,7 @@
private void leave(
ThreadNetworkController controller,
OutcomeReceiver<Void, ThreadNetworkException> receiver) {
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED, () -> controller.leave(mExecutor, receiver));
+ runAsShell(THREAD_NETWORK_PRIVILEGED, () -> controller.leave(mExecutor, receiver));
}
private void scheduleMigration(
@@ -231,7 +893,7 @@
PendingOperationalDataset pendingDataset,
OutcomeReceiver<Void, ThreadNetworkException> receiver) {
runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ THREAD_NETWORK_PRIVILEGED,
() -> controller.scheduleMigration(pendingDataset, mExecutor, receiver));
}
@@ -274,7 +936,7 @@
throws Exception {
CompletableFuture<Void> setFuture = new CompletableFuture<>();
runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ THREAD_NETWORK_PRIVILEGED,
() -> controller.setEnabled(enabled, mExecutor, newOutcomeReceiver(setFuture)));
setFuture.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
waitForEnabledState(controller, booleanToEnabledState(enabled));
@@ -285,7 +947,7 @@
ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
CompletableFuture<Void> joinFuture = new CompletableFuture<>();
runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
+ THREAD_NETWORK_PRIVILEGED,
() -> controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
return joinFuture;
}
@@ -293,17 +955,25 @@
private void joinRandomizedDatasetAndWait(ThreadNetworkController controller) throws Exception {
CompletableFuture<Void> joinFuture = joinRandomizedDataset(controller);
joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
- runAsShell(ACCESS_NETWORK_STATE, () -> assertThat(isAttached(controller)).isTrue());
+ assertThat(isAttached(controller)).isTrue();
}
private static ActiveOperationalDataset getActiveOperationalDataset(
ThreadNetworkController controller) throws Exception {
CompletableFuture<ActiveOperationalDataset> future = new CompletableFuture<>();
OperationalDatasetCallback callback = future::complete;
- controller.registerOperationalDatasetCallback(directExecutor(), callback);
- ActiveOperationalDataset dataset = future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
- controller.unregisterOperationalDatasetCallback(callback);
- return dataset;
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ THREAD_NETWORK_PRIVILEGED,
+ () -> controller.registerOperationalDatasetCallback(directExecutor(), callback));
+ try {
+ return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
+ } finally {
+ runAsShell(
+ ACCESS_NETWORK_STATE,
+ THREAD_NETWORK_PRIVILEGED,
+ () -> controller.unregisterOperationalDatasetCallback(callback));
+ }
}
private static PendingOperationalDataset getPendingOperationalDataset(
@@ -333,753 +1003,11 @@
};
}
- @Test
- public void getThreadVersion_returnsAtLeastThreadVersion1P3() {
- for (ThreadNetworkController controller : getAllControllers()) {
- assertThat(controller.getThreadVersion()).isAtLeast(THREAD_VERSION_1_3);
+ private static void assertDoesNotThrow(ThrowingRunnable runnable) {
+ try {
+ runnable.run();
+ } catch (Throwable e) {
+ fail("Should not have thrown " + e);
}
}
-
- @Test
- public void registerStateCallback_permissionsGranted_returnsCurrentStates() throws Exception {
- grantPermissions(ACCESS_NETWORK_STATE);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
- StateCallback callback = deviceRole::complete;
-
- try {
- controller.registerStateCallback(mExecutor, callback);
-
- assertThat(deviceRole.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS))
- .isEqualTo(DEVICE_ROLE_STOPPED);
- } finally {
- controller.unregisterStateCallback(callback);
- }
- }
- }
-
- @Test
- public void registerStateCallback_returnsUpdatedEnabledStates() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
- CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
- EnabledStateListener listener = new EnabledStateListener(controller);
-
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> {
- controller.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture1));
- });
- setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
-
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> {
- controller.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture2));
- });
- setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
-
- listener.expectThreadEnabledState(STATE_ENABLED);
- listener.expectThreadEnabledState(STATE_DISABLING);
- listener.expectThreadEnabledState(STATE_DISABLED);
- listener.expectThreadEnabledState(STATE_ENABLED);
-
- listener.unregisterStateCallback();
- }
- }
-
- @Test
- public void registerStateCallback_noPermissions_throwsSecurityException() throws Exception {
- dropAllPermissions();
-
- for (ThreadNetworkController controller : getAllControllers()) {
- assertThrows(
- SecurityException.class,
- () -> controller.registerStateCallback(mExecutor, role -> {}));
- }
- }
-
- @Test
- public void registerStateCallback_alreadyRegistered_throwsIllegalArgumentException()
- throws Exception {
- grantPermissions(ACCESS_NETWORK_STATE);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
- StateCallback callback = role -> deviceRole.complete(role);
- controller.registerStateCallback(mExecutor, callback);
-
- assertThrows(
- IllegalArgumentException.class,
- () -> controller.registerStateCallback(mExecutor, callback));
- }
- }
-
- @Test
- public void unregisterStateCallback_noPermissions_throwsSecurityException() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
- StateCallback callback = role -> deviceRole.complete(role);
- grantPermissions(ACCESS_NETWORK_STATE);
- controller.registerStateCallback(mExecutor, callback);
-
- try {
- dropAllPermissions();
- assertThrows(
- SecurityException.class,
- () -> controller.unregisterStateCallback(callback));
- } finally {
- grantPermissions(ACCESS_NETWORK_STATE);
- controller.unregisterStateCallback(callback);
- }
- }
- }
-
- @Test
- public void unregisterStateCallback_callbackRegistered_success() throws Exception {
- grantPermissions(ACCESS_NETWORK_STATE);
- for (ThreadNetworkController controller : getAllControllers()) {
- CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
- StateCallback callback = role -> deviceRole.complete(role);
- controller.registerStateCallback(mExecutor, callback);
-
- controller.unregisterStateCallback(callback);
- }
- }
-
- @Test
- public void unregisterStateCallback_callbackNotRegistered_throwsIllegalArgumentException()
- throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
- StateCallback callback = role -> deviceRole.complete(role);
-
- assertThrows(
- IllegalArgumentException.class,
- () -> controller.unregisterStateCallback(callback));
- }
- }
-
- @Test
- public void unregisterStateCallback_alreadyUnregistered_throwsIllegalArgumentException()
- throws Exception {
- grantPermissions(ACCESS_NETWORK_STATE);
- for (ThreadNetworkController controller : getAllControllers()) {
- CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
- StateCallback callback = deviceRole::complete;
- controller.registerStateCallback(mExecutor, callback);
- controller.unregisterStateCallback(callback);
-
- assertThrows(
- IllegalArgumentException.class,
- () -> controller.unregisterStateCallback(callback));
- }
- }
-
- @Test
- public void registerOperationalDatasetCallback_permissionsGranted_returnsCurrentStates()
- throws Exception {
- grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
- CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
- var callback = newDatasetCallback(activeFuture, pendingFuture);
-
- try {
- controller.registerOperationalDatasetCallback(mExecutor, callback);
-
- assertThat(activeFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
- assertThat(pendingFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
- } finally {
- controller.unregisterOperationalDatasetCallback(callback);
- }
- }
- }
-
- @Test
- public void registerOperationalDatasetCallback_noPermissions_throwsSecurityException()
- throws Exception {
- dropAllPermissions();
-
- for (ThreadNetworkController controller : getAllControllers()) {
- CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
- CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
- var callback = newDatasetCallback(activeFuture, pendingFuture);
-
- assertThrows(
- SecurityException.class,
- () -> controller.registerOperationalDatasetCallback(mExecutor, callback));
- }
- }
-
- @Test
- public void unregisterOperationalDatasetCallback_callbackRegistered_success() throws Exception {
- grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
- for (ThreadNetworkController controller : getAllControllers()) {
- CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
- CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
- var callback = newDatasetCallback(activeFuture, pendingFuture);
- controller.registerOperationalDatasetCallback(mExecutor, callback);
-
- controller.unregisterOperationalDatasetCallback(callback);
- }
- }
-
- @Test
- public void unregisterOperationalDatasetCallback_noPermissions_throwsSecurityException()
- throws Exception {
- dropAllPermissions();
-
- for (ThreadNetworkController controller : getAllControllers()) {
- CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
- CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
- var callback = newDatasetCallback(activeFuture, pendingFuture);
- grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
- controller.registerOperationalDatasetCallback(mExecutor, callback);
-
- try {
- dropAllPermissions();
- assertThrows(
- SecurityException.class,
- () -> controller.unregisterOperationalDatasetCallback(callback));
- } finally {
- grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
- controller.unregisterOperationalDatasetCallback(callback);
- }
- }
- }
-
- private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
- CompletableFuture<V> future) {
- return new OutcomeReceiver<V, ThreadNetworkException>() {
- @Override
- public void onResult(V result) {
- future.complete(result);
- }
-
- @Override
- public void onError(ThreadNetworkException e) {
- future.completeExceptionally(e);
- }
- };
- }
-
- @Test
- public void join_withPrivilegedPermission_success() throws Exception {
- grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
- CompletableFuture<Void> joinFuture = new CompletableFuture<>();
-
- controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
- joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
-
- grantPermissions(ACCESS_NETWORK_STATE);
- assertThat(isAttached(controller)).isTrue();
- assertThat(getActiveOperationalDataset(controller)).isEqualTo(activeDataset);
- }
- }
-
- @Test
- public void join_withoutPrivilegedPermission_throwsSecurityException() throws Exception {
- dropAllPermissions();
-
- for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
-
- assertThrows(
- SecurityException.class,
- () -> controller.join(activeDataset, mExecutor, v -> {}));
- }
- }
-
- @Test
- public void join_threadDisabled_failsWithErrorThreadDisabled() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- setEnabledAndWait(controller, false);
-
- CompletableFuture<Void> joinFuture = joinRandomizedDataset(controller);
-
- ThreadNetworkException thrown =
- (ThreadNetworkException)
- assertThrows(ExecutionException.class, joinFuture::get).getCause();
- assertThat(thrown.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED);
- }
- }
-
- @Test
- public void join_concurrentRequests_firstOneIsAborted() throws Exception {
- grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- final byte[] KEY_1 = new byte[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
- final byte[] KEY_2 = new byte[] {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
- for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset1 =
- new ActiveOperationalDataset.Builder(
- newRandomizedDataset("TestNet", controller))
- .setNetworkKey(KEY_1)
- .build();
- ActiveOperationalDataset activeDataset2 =
- new ActiveOperationalDataset.Builder(activeDataset1)
- .setNetworkKey(KEY_2)
- .build();
- CompletableFuture<Void> joinFuture1 = new CompletableFuture<>();
- CompletableFuture<Void> joinFuture2 = new CompletableFuture<>();
-
- controller.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture1));
- controller.join(activeDataset2, mExecutor, newOutcomeReceiver(joinFuture2));
-
- ThreadNetworkException thrown =
- (ThreadNetworkException)
- assertThrows(ExecutionException.class, joinFuture1::get).getCause();
- assertThat(thrown.getErrorCode()).isEqualTo(ERROR_ABORTED);
- joinFuture2.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
- grantPermissions(ACCESS_NETWORK_STATE);
- assertThat(isAttached(controller)).isTrue();
- assertThat(getActiveOperationalDataset(controller)).isEqualTo(activeDataset2);
- }
- }
-
- @Test
- public void leave_withPrivilegedPermission_success() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- joinRandomizedDatasetAndWait(controller);
-
- CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
- leave(controller, newOutcomeReceiver(leaveFuture));
- leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
-
- grantPermissions(ACCESS_NETWORK_STATE);
- assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED);
- }
- }
-
- @Test
- public void leave_withoutPrivilegedPermission_throwsSecurityException() {
- dropAllPermissions();
-
- for (ThreadNetworkController controller : getAllControllers()) {
- assertThrows(SecurityException.class, () -> controller.leave(mExecutor, v -> {}));
- }
- }
-
- @Test
- public void leave_threadDisabled_success() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- joinRandomizedDatasetAndWait(controller);
-
- CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
- setEnabledAndWait(controller, false);
- leave(controller, newOutcomeReceiver(leaveFuture));
-
- leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
- runAsShell(
- ACCESS_NETWORK_STATE,
- () -> assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED));
- }
- }
-
- @Test
- public void leave_concurrentRequests_bothSuccess() throws Exception {
- grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
- CompletableFuture<Void> joinFuture = new CompletableFuture<>();
- CompletableFuture<Void> leaveFuture1 = new CompletableFuture<>();
- CompletableFuture<Void> leaveFuture2 = new CompletableFuture<>();
- controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
- joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
-
- controller.leave(mExecutor, newOutcomeReceiver(leaveFuture1));
- controller.leave(mExecutor, newOutcomeReceiver(leaveFuture2));
-
- leaveFuture1.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
- leaveFuture2.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
- grantPermissions(ACCESS_NETWORK_STATE);
- assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED);
- }
- }
-
- @Test
- public void scheduleMigration_withPrivilegedPermission_newDatasetApplied() throws Exception {
- grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset1 =
- new ActiveOperationalDataset.Builder(
- newRandomizedDataset("TestNet", controller))
- .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
- .setExtendedPanId(new byte[] {1, 1, 1, 1, 1, 1, 1, 1})
- .build();
- ActiveOperationalDataset activeDataset2 =
- new ActiveOperationalDataset.Builder(activeDataset1)
- .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
- .setNetworkName("ThreadNet2")
- .build();
- PendingOperationalDataset pendingDataset2 =
- new PendingOperationalDataset(
- activeDataset2,
- OperationalDatasetTimestamp.fromInstant(Instant.now()),
- Duration.ofSeconds(30));
- CompletableFuture<Void> joinFuture = new CompletableFuture<>();
- CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
- controller.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture));
- joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
-
- controller.scheduleMigration(
- pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture));
- migrateFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
-
- CompletableFuture<Boolean> dataset2IsApplied = new CompletableFuture<>();
- CompletableFuture<Boolean> pendingDatasetIsRemoved = new CompletableFuture<>();
- OperationalDatasetCallback datasetCallback =
- new OperationalDatasetCallback() {
- @Override
- public void onActiveOperationalDatasetChanged(
- ActiveOperationalDataset activeDataset) {
- if (activeDataset.equals(activeDataset2)) {
- dataset2IsApplied.complete(true);
- }
- }
-
- @Override
- public void onPendingOperationalDatasetChanged(
- PendingOperationalDataset pendingDataset) {
- if (pendingDataset == null) {
- pendingDatasetIsRemoved.complete(true);
- }
- }
- };
- controller.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
- assertThat(dataset2IsApplied.get(MIGRATION_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
- assertThat(pendingDatasetIsRemoved.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
- controller.unregisterOperationalDatasetCallback(datasetCallback);
- }
- }
-
- @Test
- public void scheduleMigration_whenNotAttached_failWithPreconditionError() throws Exception {
- grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- PendingOperationalDataset pendingDataset =
- new PendingOperationalDataset(
- newRandomizedDataset("TestNet", controller),
- OperationalDatasetTimestamp.fromInstant(Instant.now()),
- Duration.ofSeconds(30));
- CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
-
- controller.scheduleMigration(
- pendingDataset, mExecutor, newOutcomeReceiver(migrateFuture));
-
- ThreadNetworkException thrown =
- (ThreadNetworkException)
- assertThrows(ExecutionException.class, migrateFuture::get).getCause();
- assertThat(thrown.getErrorCode()).isEqualTo(ERROR_FAILED_PRECONDITION);
- }
- }
-
- @Test
- public void scheduleMigration_secondRequestHasSmallerTimestamp_rejectedByLeader()
- throws Exception {
- grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- final ActiveOperationalDataset activeDataset =
- new ActiveOperationalDataset.Builder(
- newRandomizedDataset("testNet", controller))
- .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
- .build();
- ActiveOperationalDataset activeDataset1 =
- new ActiveOperationalDataset.Builder(activeDataset)
- .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
- .setNetworkName("testNet1")
- .build();
- PendingOperationalDataset pendingDataset1 =
- new PendingOperationalDataset(
- activeDataset1,
- new OperationalDatasetTimestamp(100, 0, false),
- Duration.ofSeconds(30));
- ActiveOperationalDataset activeDataset2 =
- new ActiveOperationalDataset.Builder(activeDataset)
- .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false))
- .setNetworkName("testNet2")
- .build();
- PendingOperationalDataset pendingDataset2 =
- new PendingOperationalDataset(
- activeDataset2,
- new OperationalDatasetTimestamp(20, 0, false),
- Duration.ofSeconds(30));
- CompletableFuture<Void> joinFuture = new CompletableFuture<>();
- CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>();
- CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>();
- controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
- joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
-
- controller.scheduleMigration(
- pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
- migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
- controller.scheduleMigration(
- pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
-
- ThreadNetworkException thrown =
- (ThreadNetworkException)
- assertThrows(ExecutionException.class, migrateFuture2::get).getCause();
- assertThat(thrown.getErrorCode()).isEqualTo(ERROR_REJECTED_BY_PEER);
- }
- }
-
- @Test
- public void scheduleMigration_secondRequestHasLargerTimestamp_newDatasetApplied()
- throws Exception {
- grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
-
- for (ThreadNetworkController controller : getAllControllers()) {
- final ActiveOperationalDataset activeDataset =
- new ActiveOperationalDataset.Builder(
- newRandomizedDataset("validName", controller))
- .setActiveTimestamp(new OperationalDatasetTimestamp(1L, 0, false))
- .build();
- ActiveOperationalDataset activeDataset1 =
- new ActiveOperationalDataset.Builder(activeDataset)
- .setActiveTimestamp(new OperationalDatasetTimestamp(2L, 0, false))
- .setNetworkName("testNet1")
- .build();
- PendingOperationalDataset pendingDataset1 =
- new PendingOperationalDataset(
- activeDataset1,
- new OperationalDatasetTimestamp(100, 0, false),
- Duration.ofSeconds(30));
- ActiveOperationalDataset activeDataset2 =
- new ActiveOperationalDataset.Builder(activeDataset)
- .setActiveTimestamp(new OperationalDatasetTimestamp(3L, 0, false))
- .setNetworkName("testNet2")
- .build();
- PendingOperationalDataset pendingDataset2 =
- new PendingOperationalDataset(
- activeDataset2,
- new OperationalDatasetTimestamp(200, 0, false),
- Duration.ofSeconds(30));
- CompletableFuture<Void> joinFuture = new CompletableFuture<>();
- CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>();
- CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>();
- controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
- joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
-
- controller.scheduleMigration(
- pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
- migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
- controller.scheduleMigration(
- pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
- migrateFuture2.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
-
- CompletableFuture<Boolean> dataset2IsApplied = new CompletableFuture<>();
- CompletableFuture<Boolean> pendingDatasetIsRemoved = new CompletableFuture<>();
- OperationalDatasetCallback datasetCallback =
- new OperationalDatasetCallback() {
- @Override
- public void onActiveOperationalDatasetChanged(
- ActiveOperationalDataset activeDataset) {
- if (activeDataset.equals(activeDataset2)) {
- dataset2IsApplied.complete(true);
- }
- }
-
- @Override
- public void onPendingOperationalDatasetChanged(
- PendingOperationalDataset pendingDataset) {
- if (pendingDataset == null) {
- pendingDatasetIsRemoved.complete(true);
- }
- }
- };
- controller.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
- assertThat(dataset2IsApplied.get(MIGRATION_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
- assertThat(pendingDatasetIsRemoved.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isTrue();
- controller.unregisterOperationalDatasetCallback(datasetCallback);
- }
- }
-
- @Test
- public void scheduleMigration_threadDisabled_failsWithErrorThreadDisabled() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
- PendingOperationalDataset pendingDataset =
- new PendingOperationalDataset(
- activeDataset,
- OperationalDatasetTimestamp.fromInstant(Instant.now()),
- Duration.ofSeconds(30));
- joinRandomizedDatasetAndWait(controller);
- CompletableFuture<Void> migrationFuture = new CompletableFuture<>();
-
- setEnabledAndWait(controller, false);
-
- scheduleMigration(controller, pendingDataset, newOutcomeReceiver(migrationFuture));
-
- ThreadNetworkException thrown =
- (ThreadNetworkException)
- assertThrows(ExecutionException.class, migrationFuture::get).getCause();
- assertThat(thrown.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED);
- }
- }
-
- @Test
- public void createRandomizedDataset_wrongNetworkNameLength_throwsIllegalArgumentException() {
- for (ThreadNetworkController controller : getAllControllers()) {
- assertThrows(
- IllegalArgumentException.class,
- () -> controller.createRandomizedDataset("", mExecutor, dataset -> {}));
-
- assertThrows(
- IllegalArgumentException.class,
- () ->
- controller.createRandomizedDataset(
- "ANetNameIs17Bytes", mExecutor, dataset -> {}));
- }
- }
-
- @Test
- public void createRandomizedDataset_validNetworkName_success() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- ActiveOperationalDataset dataset = newRandomizedDataset("validName", controller);
-
- assertThat(dataset.getNetworkName()).isEqualTo("validName");
- assertThat(dataset.getPanId()).isLessThan(0xffff);
- assertThat(dataset.getChannelMask().size()).isAtLeast(1);
- assertThat(dataset.getExtendedPanId()).hasLength(8);
- assertThat(dataset.getNetworkKey()).hasLength(16);
- assertThat(dataset.getPskc()).hasLength(16);
- assertThat(dataset.getMeshLocalPrefix().getPrefixLength()).isEqualTo(64);
- assertThat(dataset.getMeshLocalPrefix().getRawAddress()[0]).isEqualTo((byte) 0xfd);
- }
- }
-
- @Test
- public void setEnabled_permissionsGranted_succeeds() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
- CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
-
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> controller.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture1)));
- setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
- waitForEnabledState(controller, booleanToEnabledState(false));
-
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> controller.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture2)));
- setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
- waitForEnabledState(controller, booleanToEnabledState(true));
- }
- }
-
- @Test
- public void setEnabled_noPermissions_throwsSecurityException() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- CompletableFuture<Void> setFuture = new CompletableFuture<>();
- assertThrows(
- SecurityException.class,
- () -> controller.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture)));
- }
- }
-
- @Test
- public void setEnabled_disable_leavesThreadNetwork() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- joinRandomizedDatasetAndWait(controller);
-
- setEnabledAndWait(controller, false);
-
- runAsShell(
- ACCESS_NETWORK_STATE,
- () -> assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED));
- }
- }
-
- @Test
- public void setEnabled_toggleAfterJoin_joinsThreadNetworkAgain() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- joinRandomizedDatasetAndWait(controller);
-
- setEnabledAndWait(controller, false);
-
- runAsShell(
- ACCESS_NETWORK_STATE,
- () -> assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED));
-
- setEnabledAndWait(controller, true);
-
- runAsShell(ACCESS_NETWORK_STATE, () -> waitForAttachedState(controller));
- }
- }
-
- @Test
- public void setEnabled_enableFollowedByDisable_allSucceed() throws Exception {
- for (ThreadNetworkController controller : getAllControllers()) {
- joinRandomizedDatasetAndWait(controller);
- CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
- CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
- EnabledStateListener listener = new EnabledStateListener(controller);
- listener.expectThreadEnabledState(STATE_ENABLED);
-
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> {
- controller.setEnabled(true, mExecutor, newOutcomeReceiver(setFuture1));
- controller.setEnabled(false, mExecutor, newOutcomeReceiver(setFuture2));
- });
-
- setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
- setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
-
- listener.expectThreadEnabledState(STATE_DISABLING);
- listener.expectThreadEnabledState(STATE_DISABLED);
-
- runAsShell(
- ACCESS_NETWORK_STATE,
- () -> assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED));
-
- listener.unregisterStateCallback();
- }
- }
- // TODO (b/322437869): add test case to verify when Thread is in DISABLING state, any commands
- // (join/leave/scheduleMigration/setEnabled) fail with ERROR_BUSY. This is not currently tested
- // because DISABLING has very short lifecycle, it's not possible to guarantee the command can be
- // sent before state changes to DISABLED.
-
- @Test
- public void threadNetworkCallback_deviceAttached_threadNetworkIsAvailable() throws Exception {
- ThreadNetworkController controller = mManager.getAllThreadNetworkControllers().get(0);
- ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
- CompletableFuture<Void> joinFuture = new CompletableFuture<>();
- CompletableFuture<Network> networkFuture = new CompletableFuture<>();
- ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
- NetworkRequest networkRequest =
- new NetworkRequest.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
- .build();
- ConnectivityManager.NetworkCallback networkCallback =
- new ConnectivityManager.NetworkCallback() {
- @Override
- public void onAvailable(Network network) {
- networkFuture.complete(network);
- }
- };
-
- runAsShell(
- PERMISSION_THREAD_NETWORK_PRIVILEGED,
- () -> controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
- runAsShell(
- ACCESS_NETWORK_STATE,
- () -> cm.registerNetworkCallback(networkRequest, networkCallback));
-
- joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
- runAsShell(ACCESS_NETWORK_STATE, () -> assertThat(isAttached(controller)).isTrue());
- assertThat(networkFuture.get(NETWORK_CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNotNull();
- }
}
diff --git a/thread/tests/integration/Android.bp b/thread/tests/integration/Android.bp
index 405fb76..633389f 100644
--- a/thread/tests/integration/Android.bp
+++ b/thread/tests/integration/Android.bp
@@ -43,7 +43,7 @@
manifest: "AndroidManifest.xml",
defaults: [
"framework-connectivity-test-defaults",
- "ThreadNetworkIntegrationTestsDefaults"
+ "ThreadNetworkIntegrationTestsDefaults",
],
test_suites: [
"general-tests",