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:
parent
869394241e
commit
0797d9d501
6 changed files with 505 additions and 126 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -22,4 +22,5 @@ package android.net.thread;
|
|||
oneway interface IStateCallback {
|
||||
void onDeviceRoleChanged(int deviceRole);
|
||||
void onPartitionIdChanged(long partitionId);
|
||||
void onThreadEnableStateChanged(int enabledState);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,4 +42,6 @@ interface IThreadNetworkController {
|
|||
|
||||
int getThreadVersion();
|
||||
void createRandomizedDataset(String networkName, IActiveOperationalDatasetReceiver receiver);
|
||||
|
||||
void setEnabled(boolean enabled, in IOperationReceiver receiver);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue