Add setEnabled API

setEnabled API is added to ThreadNetworkController, this
can be used by applications with privileged permission to set
Thread to enable/disabled state.

The Thread enabled state can be subscribed with
ThreadNetworkController#registerStateCallback.

When Thread is disabled, the join/scheduleMigration APIs will
fail with ERROR_THREAD_DISABLED.

When Thread is disabling, all commands will fail with ERROR_BUSY.

Bug: 299243765

Test: atest CtsThreadNetworkTestCases:android.net.thread.cts.ThreadNetworkControllerTest

Change-Id: Ifa7845bf1d5664ecd31ce74e24b3a839f92bba13
This commit is contained in:
Yang Sun 2024-01-06 17:41:46 +08:00
commit 0797d9d501
6 changed files with 505 additions and 126 deletions

View file

@ -500,6 +500,7 @@ package android.net.thread {
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void registerOperationalDatasetCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.StateCallback);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void scheduleMigration(@NonNull android.net.thread.PendingOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void setEnabled(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void unregisterOperationalDatasetCallback(@NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void unregisterStateCallback(@NonNull android.net.thread.ThreadNetworkController.StateCallback);
field public static final int DEVICE_ROLE_CHILD = 2; // 0x2
@ -507,6 +508,9 @@ package android.net.thread {
field public static final int DEVICE_ROLE_LEADER = 4; // 0x4
field public static final int DEVICE_ROLE_ROUTER = 3; // 0x3
field public static final int DEVICE_ROLE_STOPPED = 0; // 0x0
field public static final int STATE_DISABLED = 0; // 0x0
field public static final int STATE_DISABLING = 2; // 0x2
field public static final int STATE_ENABLED = 1; // 0x1
field public static final int THREAD_VERSION_1_3 = 4; // 0x4
}
@ -518,6 +522,7 @@ package android.net.thread {
public static interface ThreadNetworkController.StateCallback {
method public void onDeviceRoleChanged(int);
method public default void onPartitionIdChanged(long);
method public default void onThreadEnableStateChanged(int);
}
@FlaggedApi("com.android.net.thread.flags.thread_enabled") public class ThreadNetworkException extends java.lang.Exception {
@ -530,6 +535,7 @@ package android.net.thread {
field public static final int ERROR_REJECTED_BY_PEER = 8; // 0x8
field public static final int ERROR_RESOURCE_EXHAUSTED = 10; // 0xa
field public static final int ERROR_RESPONSE_BAD_FORMAT = 9; // 0x9
field public static final int ERROR_THREAD_DISABLED = 12; // 0xc
field public static final int ERROR_TIMEOUT = 3; // 0x3
field public static final int ERROR_UNAVAILABLE = 4; // 0x4
field public static final int ERROR_UNKNOWN = 11; // 0xb

View file

@ -22,4 +22,5 @@ package android.net.thread;
oneway interface IStateCallback {
void onDeviceRoleChanged(int deviceRole);
void onPartitionIdChanged(long partitionId);
void onThreadEnableStateChanged(int enabledState);
}

View file

@ -42,4 +42,6 @@ interface IThreadNetworkController {
int getThreadVersion();
void createRandomizedDataset(String networkName, IActiveOperationalDatasetReceiver receiver);
void setEnabled(boolean enabled, in IOperationReceiver receiver);
}

View file

@ -68,6 +68,15 @@ public final class ThreadNetworkController {
/** The device is a Thread Leader. */
public static final int DEVICE_ROLE_LEADER = 4;
/** The Thread radio is disabled. */
public static final int STATE_DISABLED = 0;
/** The Thread radio is enabled. */
public static final int STATE_ENABLED = 1;
/** The Thread radio is being disabled. */
public static final int STATE_DISABLING = 2;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@ -79,6 +88,13 @@ public final class ThreadNetworkController {
})
public @interface DeviceRole {}
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(
prefix = {"STATE_"},
value = {STATE_DISABLED, STATE_ENABLED, STATE_DISABLING})
public @interface EnabledState {}
/** Thread standard version 1.3. */
public static final int THREAD_VERSION_1_3 = 4;
@ -106,6 +122,40 @@ public final class ThreadNetworkController {
mControllerService = controllerService;
}
/**
* Enables/Disables the radio of this ThreadNetworkController. The requested enabled state will
* be persistent and survives device reboots.
*
* <p>When Thread is in {@code STATE_DISABLED}, {@link ThreadNetworkController} APIs which
* require the Thread radio will fail with error code {@link
* ThreadNetworkException#ERROR_THREAD_DISABLED}. When Thread is in {@code STATE_DISABLING},
* {@link ThreadNetworkController} APIs that return a {@link ThreadNetworkException} will fail
* with error code {@link ThreadNetworkException#ERROR_BUSY}.
*
* <p>On success, {@link OutcomeReceiver#onResult} of {@code receiver} is called. It indicates
* the operation has completed. But there maybe subsequent calls to update the enabled state,
* callers of this method should use {@link #registerStateCallback} to subscribe to the Thread
* enabled state changes.
*
* <p>On failure, {@link OutcomeReceiver#onError} of {@code receiver} will be invoked with a
* specific error in {@link ThreadNetworkException#ERROR_}.
*
* @param enabled {@code true} for enabling Thread
* @param executor the executor to execute {@code receiver}
* @param receiver the receiver to receive result of this operation
*/
@RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED")
public void setEnabled(
boolean enabled,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
try {
mControllerService.setEnabled(enabled, new OperationReceiverProxy(executor, receiver));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** Returns the Thread version this device is operating on. */
@ThreadVersion
public int getThreadVersion() {
@ -170,6 +220,16 @@ public final class ThreadNetworkController {
* @param partitionId the new Thread partition ID
*/
default void onPartitionIdChanged(long partitionId) {}
/**
* The Thread enabled state has changed.
*
* <p>The Thread enabled state can be set with {@link setEnabled}, it may also be updated by
* airplane mode or admin control.
*
* @param enabledState the new Thread enabled state
*/
default void onThreadEnableStateChanged(@EnabledState int enabledState) {}
}
private static final class StateCallbackProxy extends IStateCallback.Stub {
@ -200,6 +260,16 @@ public final class ThreadNetworkController {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void onThreadEnableStateChanged(@EnabledState int enabled) {
final long identity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mCallback.onThreadEnableStateChanged(enabled));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
/**

View file

@ -48,6 +48,7 @@ public class ThreadNetworkException extends Exception {
ERROR_RESPONSE_BAD_FORMAT,
ERROR_RESOURCE_EXHAUSTED,
ERROR_UNKNOWN,
ERROR_THREAD_DISABLED,
})
public @interface ErrorCode {}
@ -129,6 +130,13 @@ public class ThreadNetworkException extends Exception {
*/
public static final int ERROR_UNKNOWN = 11;
/**
* The operation failed because the Thread radio is disabled by {@link
* ThreadNetworkController#setEnabled}, airplane mode or device admin. The caller should retry
* only after Thread is enabled.
*/
public static final int ERROR_THREAD_DISABLED = 12;
private final int mErrorCode;
/** Creates a new {@link ThreadNetworkException} object with given error code and message. */

View file

@ -16,11 +16,19 @@
package android.net.thread.cts;
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_CHILD;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_ROUTER;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
import static android.net.thread.ThreadNetworkController.STATE_DISABLING;
import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
import static android.net.thread.ThreadNetworkController.THREAD_VERSION_1_3;
import static android.net.thread.ThreadNetworkException.ERROR_ABORTED;
import static android.net.thread.ThreadNetworkException.ERROR_FAILED_PRECONDITION;
import static android.net.thread.ThreadNetworkException.ERROR_REJECTED_BY_PEER;
import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@ -29,12 +37,12 @@ import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assume.assumeNotNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.Manifest.permission;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
@ -54,12 +62,11 @@ import android.os.OutcomeReceiver;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import com.android.net.module.util.ArrayTrackRecord;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
import com.google.common.util.concurrent.SettableFuture;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@ -68,9 +75,11 @@ import org.junit.runner.RunWith;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -81,8 +90,11 @@ import java.util.concurrent.Executors;
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) // Thread is available on only U+
public class ThreadNetworkControllerTest {
private static final int JOIN_TIMEOUT_MILLIS = 30 * 1000;
private static final int LEAVE_TIMEOUT_MILLIS = 2_000;
private static final int MIGRATION_TIMEOUT_MILLIS = 40 * 1_000;
private static final int NETWORK_CALLBACK_TIMEOUT_MILLIS = 10 * 1000;
private static final int CALLBACK_TIMEOUT_MILLIS = 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 =
"android.permission.THREAD_NETWORK_PRIVILEGED";
@ -95,7 +107,7 @@ public class ThreadNetworkControllerTest {
private Set<String> mGrantedPermissions;
@Before
public void setUp() {
public void setUp() throws Exception {
mExecutor = Executors.newSingleThreadExecutor();
mManager = mContext.getSystemService(ThreadNetworkManager.class);
mGrantedPermissions = new HashSet<String>();
@ -103,13 +115,17 @@ public class ThreadNetworkControllerTest {
// 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);
for (ThreadNetworkController controller : getAllControllers()) {
setEnabledAndWait(controller, true);
}
}
@After
public void tearDown() throws Exception {
if (mManager != null) {
leaveAndWait();
dropAllPermissions();
leaveAndWait();
}
}
@ -118,12 +134,10 @@ public class ThreadNetworkControllerTest {
}
private void leaveAndWait() throws Exception {
grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
for (ThreadNetworkController controller : getAllControllers()) {
SettableFuture<Void> future = SettableFuture.create();
controller.leave(mExecutor, future::set);
future.get();
CompletableFuture<Void> future = new CompletableFuture<>();
leave(controller, future::complete);
future.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
}
}
@ -142,8 +156,8 @@ public class ThreadNetworkControllerTest {
private static ActiveOperationalDataset newRandomizedDataset(
String networkName, ThreadNetworkController controller) throws Exception {
SettableFuture<ActiveOperationalDataset> future = SettableFuture.create();
controller.createRandomizedDataset(networkName, directExecutor(), future::set);
CompletableFuture<ActiveOperationalDataset> future = new CompletableFuture<>();
controller.createRandomizedDataset(networkName, directExecutor(), future::complete);
return future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
}
@ -152,33 +166,140 @@ public class ThreadNetworkControllerTest {
}
private static int getDeviceRole(ThreadNetworkController controller) throws Exception {
SettableFuture<Integer> future = SettableFuture.create();
StateCallback callback = future::set;
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;
}
private static int waitForAttachedState(ThreadNetworkController controller) throws Exception {
List<Integer> attachedRoles = new ArrayList<>();
attachedRoles.add(DEVICE_ROLE_CHILD);
attachedRoles.add(DEVICE_ROLE_ROUTER);
attachedRoles.add(DEVICE_ROLE_LEADER);
return waitForStateAnyOf(controller, attachedRoles);
}
private static int waitForStateAnyOf(
ThreadNetworkController controller, List<Integer> deviceRoles) throws Exception {
SettableFuture<Integer> future = SettableFuture.create();
CompletableFuture<Integer> future = new CompletableFuture<>();
StateCallback callback =
newRole -> {
if (deviceRoles.contains(newRole)) {
future.set(newRole);
future.complete(newRole);
}
};
controller.registerStateCallback(directExecutor(), callback);
int role = future.get();
int role = future.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
controller.unregisterStateCallback(callback);
return role;
}
private static void waitForEnabledState(ThreadNetworkController controller, int state)
throws Exception {
CompletableFuture<Integer> future = new CompletableFuture<>();
StateCallback callback =
new ThreadNetworkController.StateCallback() {
@Override
public void onDeviceRoleChanged(int r) {}
@Override
public void onThreadEnableStateChanged(int enabled) {
if (enabled == state) {
future.complete(enabled);
}
}
};
runAsShell(
ACCESS_NETWORK_STATE,
() -> controller.registerStateCallback(directExecutor(), callback));
future.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
runAsShell(ACCESS_NETWORK_STATE, () -> controller.unregisterStateCallback(callback));
}
private void leave(
ThreadNetworkController controller,
OutcomeReceiver<Void, ThreadNetworkException> receiver) {
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED, () -> controller.leave(mExecutor, receiver));
}
private void scheduleMigration(
ThreadNetworkController controller,
PendingOperationalDataset pendingDataset,
OutcomeReceiver<Void, ThreadNetworkException> receiver) {
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
() -> controller.scheduleMigration(pendingDataset, mExecutor, receiver));
}
private class EnabledStateListener {
private ArrayTrackRecord<Integer> mEnabledStates = new ArrayTrackRecord<>();
private final ArrayTrackRecord<Integer>.ReadHead mReadHead = mEnabledStates.newReadHead();
ThreadNetworkController mController;
StateCallback mCallback =
new ThreadNetworkController.StateCallback() {
@Override
public void onDeviceRoleChanged(int r) {}
@Override
public void onThreadEnableStateChanged(int enabled) {
mEnabledStates.add(enabled);
}
};
EnabledStateListener(ThreadNetworkController controller) {
this.mController = controller;
runAsShell(
ACCESS_NETWORK_STATE,
() -> controller.registerStateCallback(mExecutor, mCallback));
}
public void expectThreadEnabledState(int enabled) {
assertNotNull(mReadHead.poll(ENABLED_TIMEOUT_MILLIS, e -> (e == enabled)));
}
public void unregisterStateCallback() {
runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(mCallback));
}
}
private int booleanToEnabledState(boolean enabled) {
return enabled ? STATE_ENABLED : STATE_DISABLED;
}
private void setEnabledAndWait(ThreadNetworkController controller, boolean enabled)
throws Exception {
CompletableFuture<Void> setFuture = new CompletableFuture<>();
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
() -> controller.setEnabled(enabled, mExecutor, newOutcomeReceiver(setFuture)));
setFuture.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
waitForEnabledState(controller, booleanToEnabledState(enabled));
}
private CompletableFuture joinRandomizedDataset(ThreadNetworkController controller)
throws Exception {
ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
CompletableFuture<Void> joinFuture = new CompletableFuture<>();
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
() -> controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
return joinFuture;
}
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());
}
private static ActiveOperationalDataset getActiveOperationalDataset(
ThreadNetworkController controller) throws Exception {
SettableFuture<ActiveOperationalDataset> future = SettableFuture.create();
OperationalDatasetCallback callback = future::set;
CompletableFuture<ActiveOperationalDataset> future = new CompletableFuture<>();
OperationalDatasetCallback callback = future::complete;
controller.registerOperationalDatasetCallback(directExecutor(), callback);
ActiveOperationalDataset dataset = future.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
controller.unregisterOperationalDatasetCallback(callback);
@ -187,27 +308,27 @@ public class ThreadNetworkControllerTest {
private static PendingOperationalDataset getPendingOperationalDataset(
ThreadNetworkController controller) throws Exception {
SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
controller.registerOperationalDatasetCallback(
directExecutor(), newDatasetCallback(activeFuture, pendingFuture));
return pendingFuture.get();
return pendingFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
}
private static OperationalDatasetCallback newDatasetCallback(
SettableFuture<ActiveOperationalDataset> activeFuture,
SettableFuture<PendingOperationalDataset> pendingFuture) {
CompletableFuture<ActiveOperationalDataset> activeFuture,
CompletableFuture<PendingOperationalDataset> pendingFuture) {
return new OperationalDatasetCallback() {
@Override
public void onActiveOperationalDatasetChanged(
ActiveOperationalDataset activeOpDataset) {
activeFuture.set(activeOpDataset);
activeFuture.complete(activeOpDataset);
}
@Override
public void onPendingOperationalDatasetChanged(
PendingOperationalDataset pendingOpDataset) {
pendingFuture.set(pendingOpDataset);
pendingFuture.complete(pendingOpDataset);
}
};
}
@ -221,22 +342,53 @@ public class ThreadNetworkControllerTest {
@Test
public void registerStateCallback_permissionsGranted_returnsCurrentStates() throws Exception {
grantPermissions(permission.ACCESS_NETWORK_STATE);
grantPermissions(ACCESS_NETWORK_STATE);
for (ThreadNetworkController controller : getAllControllers()) {
SettableFuture<Integer> deviceRole = SettableFuture.create();
StateCallback callback = deviceRole::set;
CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
StateCallback callback = deviceRole::complete;
try {
controller.registerStateCallback(mExecutor, callback);
assertThat(deviceRole.get()).isEqualTo(DEVICE_ROLE_STOPPED);
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();
@ -251,11 +403,11 @@ public class ThreadNetworkControllerTest {
@Test
public void registerStateCallback_alreadyRegistered_throwsIllegalArgumentException()
throws Exception {
grantPermissions(permission.ACCESS_NETWORK_STATE);
grantPermissions(ACCESS_NETWORK_STATE);
for (ThreadNetworkController controller : getAllControllers()) {
SettableFuture<Integer> deviceRole = SettableFuture.create();
StateCallback callback = role -> deviceRole.set(role);
CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
StateCallback callback = role -> deviceRole.complete(role);
controller.registerStateCallback(mExecutor, callback);
assertThrows(
@ -267,9 +419,9 @@ public class ThreadNetworkControllerTest {
@Test
public void unregisterStateCallback_noPermissions_throwsSecurityException() throws Exception {
for (ThreadNetworkController controller : getAllControllers()) {
SettableFuture<Integer> deviceRole = SettableFuture.create();
StateCallback callback = role -> deviceRole.set(role);
grantPermissions(permission.ACCESS_NETWORK_STATE);
CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
StateCallback callback = role -> deviceRole.complete(role);
grantPermissions(ACCESS_NETWORK_STATE);
controller.registerStateCallback(mExecutor, callback);
try {
@ -278,7 +430,7 @@ public class ThreadNetworkControllerTest {
SecurityException.class,
() -> controller.unregisterStateCallback(callback));
} finally {
grantPermissions(permission.ACCESS_NETWORK_STATE);
grantPermissions(ACCESS_NETWORK_STATE);
controller.unregisterStateCallback(callback);
}
}
@ -286,10 +438,10 @@ public class ThreadNetworkControllerTest {
@Test
public void unregisterStateCallback_callbackRegistered_success() throws Exception {
grantPermissions(permission.ACCESS_NETWORK_STATE);
grantPermissions(ACCESS_NETWORK_STATE);
for (ThreadNetworkController controller : getAllControllers()) {
SettableFuture<Integer> deviceRole = SettableFuture.create();
StateCallback callback = role -> deviceRole.set(role);
CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
StateCallback callback = role -> deviceRole.complete(role);
controller.registerStateCallback(mExecutor, callback);
controller.unregisterStateCallback(callback);
@ -300,8 +452,8 @@ public class ThreadNetworkControllerTest {
public void unregisterStateCallback_callbackNotRegistered_throwsIllegalArgumentException()
throws Exception {
for (ThreadNetworkController controller : getAllControllers()) {
SettableFuture<Integer> deviceRole = SettableFuture.create();
StateCallback callback = role -> deviceRole.set(role);
CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
StateCallback callback = role -> deviceRole.complete(role);
assertThrows(
IllegalArgumentException.class,
@ -312,10 +464,10 @@ public class ThreadNetworkControllerTest {
@Test
public void unregisterStateCallback_alreadyUnregistered_throwsIllegalArgumentException()
throws Exception {
grantPermissions(permission.ACCESS_NETWORK_STATE);
grantPermissions(ACCESS_NETWORK_STATE);
for (ThreadNetworkController controller : getAllControllers()) {
SettableFuture<Integer> deviceRole = SettableFuture.create();
StateCallback callback = deviceRole::set;
CompletableFuture<Integer> deviceRole = new CompletableFuture<>();
StateCallback callback = deviceRole::complete;
controller.registerStateCallback(mExecutor, callback);
controller.unregisterStateCallback(callback);
@ -328,18 +480,18 @@ public class ThreadNetworkControllerTest {
@Test
public void registerOperationalDatasetCallback_permissionsGranted_returnsCurrentStates()
throws Exception {
grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
for (ThreadNetworkController controller : getAllControllers()) {
SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
var callback = newDatasetCallback(activeFuture, pendingFuture);
try {
controller.registerOperationalDatasetCallback(mExecutor, callback);
assertThat(activeFuture.get()).isNull();
assertThat(pendingFuture.get()).isNull();
assertThat(activeFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
assertThat(pendingFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNull();
} finally {
controller.unregisterOperationalDatasetCallback(callback);
}
@ -352,8 +504,8 @@ public class ThreadNetworkControllerTest {
dropAllPermissions();
for (ThreadNetworkController controller : getAllControllers()) {
SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
var callback = newDatasetCallback(activeFuture, pendingFuture);
assertThrows(
@ -364,10 +516,10 @@ public class ThreadNetworkControllerTest {
@Test
public void unregisterOperationalDatasetCallback_callbackRegistered_success() throws Exception {
grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
for (ThreadNetworkController controller : getAllControllers()) {
SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
var callback = newDatasetCallback(activeFuture, pendingFuture);
controller.registerOperationalDatasetCallback(mExecutor, callback);
@ -381,10 +533,10 @@ public class ThreadNetworkControllerTest {
dropAllPermissions();
for (ThreadNetworkController controller : getAllControllers()) {
SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
CompletableFuture<ActiveOperationalDataset> activeFuture = new CompletableFuture<>();
CompletableFuture<PendingOperationalDataset> pendingFuture = new CompletableFuture<>();
var callback = newDatasetCallback(activeFuture, pendingFuture);
grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
controller.registerOperationalDatasetCallback(mExecutor, callback);
try {
@ -393,24 +545,23 @@ public class ThreadNetworkControllerTest {
SecurityException.class,
() -> controller.unregisterOperationalDatasetCallback(callback));
} finally {
grantPermissions(
permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
controller.unregisterOperationalDatasetCallback(callback);
}
}
}
private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
SettableFuture<V> future) {
CompletableFuture<V> future) {
return new OutcomeReceiver<V, ThreadNetworkException>() {
@Override
public void onResult(V result) {
future.set(result);
future.complete(result);
}
@Override
public void onError(ThreadNetworkException e) {
future.setException(e);
future.completeExceptionally(e);
}
};
}
@ -421,12 +572,12 @@ public class ThreadNetworkControllerTest {
for (ThreadNetworkController controller : getAllControllers()) {
ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
SettableFuture<Void> joinFuture = SettableFuture.create();
CompletableFuture<Void> joinFuture = new CompletableFuture<>();
controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
joinFuture.get();
joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
grantPermissions(permission.ACCESS_NETWORK_STATE);
grantPermissions(ACCESS_NETWORK_STATE);
assertThat(isAttached(controller)).isTrue();
assertThat(getActiveOperationalDataset(controller)).isEqualTo(activeDataset);
}
@ -445,6 +596,20 @@ public class ThreadNetworkControllerTest {
}
}
@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);
@ -461,8 +626,8 @@ public class ThreadNetworkControllerTest {
new ActiveOperationalDataset.Builder(activeDataset1)
.setNetworkKey(KEY_2)
.build();
SettableFuture<Void> joinFuture1 = SettableFuture.create();
SettableFuture<Void> joinFuture2 = SettableFuture.create();
CompletableFuture<Void> joinFuture1 = new CompletableFuture<>();
CompletableFuture<Void> joinFuture2 = new CompletableFuture<>();
controller.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture1));
controller.join(activeDataset2, mExecutor, newOutcomeReceiver(joinFuture2));
@ -471,8 +636,8 @@ public class ThreadNetworkControllerTest {
(ThreadNetworkException)
assertThrows(ExecutionException.class, joinFuture1::get).getCause();
assertThat(thrown.getErrorCode()).isEqualTo(ERROR_ABORTED);
joinFuture2.get();
grantPermissions(permission.ACCESS_NETWORK_STATE);
joinFuture2.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
grantPermissions(ACCESS_NETWORK_STATE);
assertThat(isAttached(controller)).isTrue();
assertThat(getActiveOperationalDataset(controller)).isEqualTo(activeDataset2);
}
@ -480,19 +645,14 @@ public class ThreadNetworkControllerTest {
@Test
public void leave_withPrivilegedPermission_success() throws Exception {
grantPermissions(PERMISSION_THREAD_NETWORK_PRIVILEGED);
for (ThreadNetworkController controller : getAllControllers()) {
ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
SettableFuture<Void> joinFuture = SettableFuture.create();
SettableFuture<Void> leaveFuture = SettableFuture.create();
controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
joinFuture.get();
joinRandomizedDatasetAndWait(controller);
controller.leave(mExecutor, newOutcomeReceiver(leaveFuture));
leaveFuture.get();
CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
leave(controller, newOutcomeReceiver(leaveFuture));
leaveFuture.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
grantPermissions(permission.ACCESS_NETWORK_STATE);
grantPermissions(ACCESS_NETWORK_STATE);
assertThat(getDeviceRole(controller)).isEqualTo(DEVICE_ROLE_STOPPED);
}
}
@ -506,31 +666,47 @@ public class ThreadNetworkControllerTest {
}
}
@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);
SettableFuture<Void> joinFuture = SettableFuture.create();
SettableFuture<Void> leaveFuture1 = SettableFuture.create();
SettableFuture<Void> leaveFuture2 = SettableFuture.create();
CompletableFuture<Void> joinFuture = new CompletableFuture<>();
CompletableFuture<Void> leaveFuture1 = new CompletableFuture<>();
CompletableFuture<Void> leaveFuture2 = new CompletableFuture<>();
controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
joinFuture.get();
joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
controller.leave(mExecutor, newOutcomeReceiver(leaveFuture1));
controller.leave(mExecutor, newOutcomeReceiver(leaveFuture2));
leaveFuture1.get();
leaveFuture2.get();
grantPermissions(permission.ACCESS_NETWORK_STATE);
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(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
for (ThreadNetworkController controller : getAllControllers()) {
ActiveOperationalDataset activeDataset1 =
@ -549,24 +725,24 @@ public class ThreadNetworkControllerTest {
activeDataset2,
OperationalDatasetTimestamp.fromInstant(Instant.now()),
Duration.ofSeconds(30));
SettableFuture<Void> joinFuture = SettableFuture.create();
SettableFuture<Void> migrateFuture = SettableFuture.create();
CompletableFuture<Void> joinFuture = new CompletableFuture<>();
CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
controller.join(activeDataset1, mExecutor, newOutcomeReceiver(joinFuture));
joinFuture.get();
joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
controller.scheduleMigration(
pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture));
migrateFuture.get();
migrateFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
SettableFuture<Boolean> dataset2IsApplied = SettableFuture.create();
SettableFuture<Boolean> pendingDatasetIsRemoved = SettableFuture.create();
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.set(true);
dataset2IsApplied.complete(true);
}
}
@ -574,20 +750,20 @@ public class ThreadNetworkControllerTest {
public void onPendingOperationalDatasetChanged(
PendingOperationalDataset pendingDataset) {
if (pendingDataset == null) {
pendingDatasetIsRemoved.set(true);
pendingDatasetIsRemoved.complete(true);
}
}
};
controller.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
assertThat(dataset2IsApplied.get()).isTrue();
assertThat(pendingDatasetIsRemoved.get()).isTrue();
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(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
for (ThreadNetworkController controller : getAllControllers()) {
PendingOperationalDataset pendingDataset =
@ -595,7 +771,7 @@ public class ThreadNetworkControllerTest {
newRandomizedDataset("TestNet", controller),
OperationalDatasetTimestamp.fromInstant(Instant.now()),
Duration.ofSeconds(30));
SettableFuture<Void> migrateFuture = SettableFuture.create();
CompletableFuture<Void> migrateFuture = new CompletableFuture<>();
controller.scheduleMigration(
pendingDataset, mExecutor, newOutcomeReceiver(migrateFuture));
@ -610,7 +786,7 @@ public class ThreadNetworkControllerTest {
@Test
public void scheduleMigration_secondRequestHasSmallerTimestamp_rejectedByLeader()
throws Exception {
grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
for (ThreadNetworkController controller : getAllControllers()) {
final ActiveOperationalDataset activeDataset =
@ -638,15 +814,15 @@ public class ThreadNetworkControllerTest {
activeDataset2,
new OperationalDatasetTimestamp(20, 0, false),
Duration.ofSeconds(30));
SettableFuture<Void> joinFuture = SettableFuture.create();
SettableFuture<Void> migrateFuture1 = SettableFuture.create();
SettableFuture<Void> migrateFuture2 = SettableFuture.create();
CompletableFuture<Void> joinFuture = new CompletableFuture<>();
CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>();
CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>();
controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
joinFuture.get();
joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
controller.scheduleMigration(
pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
migrateFuture1.get();
migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
controller.scheduleMigration(
pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
@ -660,7 +836,7 @@ public class ThreadNetworkControllerTest {
@Test
public void scheduleMigration_secondRequestHasLargerTimestamp_newDatasetApplied()
throws Exception {
grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
grantPermissions(ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
for (ThreadNetworkController controller : getAllControllers()) {
final ActiveOperationalDataset activeDataset =
@ -688,28 +864,28 @@ public class ThreadNetworkControllerTest {
activeDataset2,
new OperationalDatasetTimestamp(200, 0, false),
Duration.ofSeconds(30));
SettableFuture<Void> joinFuture = SettableFuture.create();
SettableFuture<Void> migrateFuture1 = SettableFuture.create();
SettableFuture<Void> migrateFuture2 = SettableFuture.create();
CompletableFuture<Void> joinFuture = new CompletableFuture<>();
CompletableFuture<Void> migrateFuture1 = new CompletableFuture<>();
CompletableFuture<Void> migrateFuture2 = new CompletableFuture<>();
controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture));
joinFuture.get();
joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
controller.scheduleMigration(
pendingDataset1, mExecutor, newOutcomeReceiver(migrateFuture1));
migrateFuture1.get();
migrateFuture1.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
controller.scheduleMigration(
pendingDataset2, mExecutor, newOutcomeReceiver(migrateFuture2));
migrateFuture2.get();
migrateFuture2.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS);
SettableFuture<Boolean> dataset2IsApplied = SettableFuture.create();
SettableFuture<Boolean> pendingDatasetIsRemoved = SettableFuture.create();
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.set(true);
dataset2IsApplied.complete(true);
}
}
@ -717,17 +893,40 @@ public class ThreadNetworkControllerTest {
public void onPendingOperationalDatasetChanged(
PendingOperationalDataset pendingDataset) {
if (pendingDataset == null) {
pendingDatasetIsRemoved.set(true);
pendingDatasetIsRemoved.complete(true);
}
}
};
controller.registerOperationalDatasetCallback(directExecutor(), datasetCallback);
assertThat(dataset2IsApplied.get()).isTrue();
assertThat(pendingDatasetIsRemoved.get()).isTrue();
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()) {
@ -759,12 +958,106 @@ public class ThreadNetworkControllerTest {
}
}
@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);
SettableFuture<Void> joinFuture = SettableFuture.create();
SettableFuture<Network> networkFuture = SettableFuture.create();
CompletableFuture<Void> joinFuture = new CompletableFuture<>();
CompletableFuture<Network> networkFuture = new CompletableFuture<>();
ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
NetworkRequest networkRequest =
new NetworkRequest.Builder()
@ -774,7 +1067,7 @@ public class ThreadNetworkControllerTest {
new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
networkFuture.set(network);
networkFuture.complete(network);
}
};
@ -782,12 +1075,11 @@ public class ThreadNetworkControllerTest {
PERMISSION_THREAD_NETWORK_PRIVILEGED,
() -> controller.join(activeDataset, mExecutor, newOutcomeReceiver(joinFuture)));
runAsShell(
permission.ACCESS_NETWORK_STATE,
ACCESS_NETWORK_STATE,
() -> cm.registerNetworkCallback(networkRequest, networkCallback));
joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
runAsShell(
permission.ACCESS_NETWORK_STATE, () -> assertThat(isAttached(controller)).isTrue());
runAsShell(ACCESS_NETWORK_STATE, () -> assertThat(isAttached(controller)).isTrue());
assertThat(networkFuture.get(NETWORK_CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)).isNotNull();
}
}