[Thread] unify synchronous controller API for testing

This commit creates the new `ThreadNetworkControllerWrapper` class to
unify the asynchronous version of the ThreadNetworkController API to
simplify Thread tests.

Test: atest ThreadNetworkIntegrationTests
Change-Id: Ibc6e6e2dad5041b092315aa0278915c137867488
This commit is contained in:
Kangping Dong 2024-03-09 20:17:44 +08:00
commit 62eac0132d
4 changed files with 231 additions and 165 deletions

View file

@ -17,10 +17,7 @@
package android.net.thread;
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
import static android.net.thread.utils.IntegrationTestUtils.JOIN_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv6Packet;
import static android.net.thread.utils.IntegrationTestUtils.isFromIpv6Source;
import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
@ -36,13 +33,14 @@ import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static java.util.Objects.requireNonNull;
import android.content.Context;
import android.net.InetAddresses;
import android.net.LinkProperties;
@ -54,6 +52,7 @@ import android.net.thread.utils.ThreadFeatureCheckerRule;
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresIpv6MulticastRouting;
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresSimulationThreadDevice;
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.net.thread.utils.ThreadNetworkControllerWrapper;
import android.os.Handler;
import android.os.HandlerThread;
@ -74,9 +73,6 @@ import java.net.Inet6Address;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/** Integration test cases for Thread Border Routing feature. */
@ -108,7 +104,8 @@ public class BorderRoutingTest {
@Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
private ThreadNetworkController mController;
private final ThreadNetworkControllerWrapper mController =
ThreadNetworkControllerWrapper.newInstance(mContext);
private OtDaemonController mOtCtl;
private HandlerThread mHandlerThread;
private Handler mHandler;
@ -119,11 +116,6 @@ public class BorderRoutingTest {
@Before
public void setUp() throws Exception {
final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
if (manager != null) {
mController = manager.getAllThreadNetworkControllers().get(0);
}
// TODO: b/323301831 - This is a workaround to avoid unnecessary delay to re-form a network
mOtCtl = new OtDaemonController();
mOtCtl.factoryReset();
@ -134,9 +126,7 @@ public class BorderRoutingTest {
mFtds = new ArrayList<>();
setUpInfraNetwork();
// BR forms a network.
startBrLeader();
mController.joinAndWait(DEFAULT_DATASET);
// Creates a infra network device.
mInfraNetworkReader = newPacketReader(mInfraNetworkTracker.getTestIface(), mHandler);
@ -150,16 +140,8 @@ public class BorderRoutingTest {
@After
public void tearDown() throws Exception {
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
NETWORK_SETTINGS,
() -> {
CountDownLatch latch = new CountDownLatch(2);
mController.setTestNetworkAsUpstream(
null, directExecutor(), v -> latch.countDown());
mController.leave(directExecutor(), v -> latch.countDown());
latch.await(10, TimeUnit.SECONDS);
});
mController.setTestNetworkAsUpstreamAndWait(null);
mController.leaveAndWait();
tearDownInfraNetwork();
mHandlerThread.quitSafely();
@ -205,9 +187,6 @@ public class BorderRoutingTest {
* </pre>
*/
// Form the network.
mOtCtl.factoryReset();
startBrLeader();
startInfraDevice();
FullThreadDevice ftd = mFtds.get(0);
startFtdChild(ftd);
@ -229,12 +208,10 @@ public class BorderRoutingTest {
* </pre>
*/
// Creates a Full Thread Device (FTD) and lets it join the network.
FullThreadDevice ftd = mFtds.get(0);
startFtdChild(ftd);
Inet6Address ftdOmr = ftd.getOmrAddress();
Inet6Address ftdMlEid = ftd.getMlEid();
assertNotNull(ftdMlEid);
Inet6Address ftdOmr = requireNonNull(ftd.getOmrAddress());
Inet6Address ftdMlEid = requireNonNull(ftd.getMlEid());
ftd.udpBind(ftdOmr, 12345);
sendUdpMessage(ftdOmr, 12345, "aaaaaaaa");
@ -588,38 +565,21 @@ public class BorderRoutingTest {
pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4));
}
private void setUpInfraNetwork() {
private void setUpInfraNetwork() throws Exception {
mInfraNetworkTracker =
runAsShell(
MANAGE_TEST_NETWORKS,
() ->
initTestNetwork(
mContext, new LinkProperties(), 5000 /* timeoutMs */));
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
NETWORK_SETTINGS,
() -> {
CompletableFuture<Void> future = new CompletableFuture<>();
mController.setTestNetworkAsUpstream(
mInfraNetworkTracker.getTestIface().getInterfaceName(),
directExecutor(),
future::complete);
future.get(5, TimeUnit.SECONDS);
});
mController.setTestNetworkAsUpstreamAndWait(
mInfraNetworkTracker.getTestIface().getInterfaceName());
}
private void tearDownInfraNetwork() {
runAsShell(MANAGE_TEST_NETWORKS, () -> mInfraNetworkTracker.teardown());
}
private void startBrLeader() throws Exception {
CompletableFuture<Void> joinFuture = new CompletableFuture<>();
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
() -> mController.join(DEFAULT_DATASET, directExecutor(), joinFuture::complete));
joinFuture.get(RESTART_JOIN_TIMEOUT.toSeconds(), TimeUnit.SECONDS);
}
private void startFtdChild(FullThreadDevice ftd) throws Exception {
ftd.factoryReset();
ftd.joinNetwork(DEFAULT_DATASET);

View file

@ -16,11 +16,8 @@
package android.net.thread;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
import static android.net.thread.utils.IntegrationTestUtils.JOIN_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.SERVICE_DISCOVERY_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.discoverForServiceLost;
import static android.net.thread.utils.IntegrationTestUtils.discoverService;
@ -28,16 +25,12 @@ import static android.net.thread.utils.IntegrationTestUtils.resolveService;
import static android.net.thread.utils.IntegrationTestUtils.resolveServiceUntil;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.junit.Assert.assertThrows;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import android.content.Context;
import android.net.nsd.NsdManager;
@ -47,6 +40,7 @@ import android.net.thread.utils.TapTestNetworkTracker;
import android.net.thread.utils.ThreadFeatureCheckerRule;
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresSimulationThreadDevice;
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.net.thread.utils.ThreadNetworkControllerWrapper;
import android.os.HandlerThread;
import androidx.test.core.app.ApplicationProvider;
@ -99,27 +93,18 @@ public class ServiceDiscoveryTest {
@Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
private final ThreadNetworkControllerWrapper mController =
ThreadNetworkControllerWrapper.newInstance(mContext);
private HandlerThread mHandlerThread;
private ThreadNetworkController mController;
private NsdManager mNsdManager;
private TapTestNetworkTracker mTestNetworkTracker;
private List<FullThreadDevice> mFtds;
@Before
public void setUp() throws Exception {
final ThreadNetworkManager manager = mContext.getSystemService(ThreadNetworkManager.class);
if (manager != null) {
mController = manager.getAllThreadNetworkControllers().get(0);
}
// BR forms a network.
CompletableFuture<Void> joinFuture = new CompletableFuture<>();
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
() -> mController.join(DEFAULT_DATASET, directExecutor(), joinFuture::complete));
joinFuture.get(RESTART_JOIN_TIMEOUT.toMillis(), MILLISECONDS);
mController.joinAndWait(DEFAULT_DATASET);
mNsdManager = mContext.getSystemService(NsdManager.class);
mHandlerThread = new HandlerThread(TAG);
@ -127,17 +112,8 @@ public class ServiceDiscoveryTest {
mTestNetworkTracker = new TapTestNetworkTracker(mContext, mHandlerThread.getLooper());
assertThat(mTestNetworkTracker).isNotNull();
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
NETWORK_SETTINGS,
() -> {
CompletableFuture<Void> future = new CompletableFuture<>();
mController.setTestNetworkAsUpstream(
mTestNetworkTracker.getInterfaceName(),
directExecutor(),
v -> future.complete(null));
future.get(5, SECONDS);
});
mController.setTestNetworkAsUpstreamAndWait(mTestNetworkTracker.getInterfaceName());
// Create the FTDs in setUp() so that the FTDs can be safely released in tearDown().
// Don't create new FTDs in test cases.
mFtds = new ArrayList<>();
@ -164,18 +140,8 @@ public class ServiceDiscoveryTest {
mHandlerThread.quitSafely();
mHandlerThread.join();
}
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
NETWORK_SETTINGS,
() -> {
CompletableFuture<Void> setUpstreamFuture = new CompletableFuture<>();
CompletableFuture<Void> leaveFuture = new CompletableFuture<>();
mController.setTestNetworkAsUpstream(
null, directExecutor(), v -> setUpstreamFuture.complete(null));
mController.leave(directExecutor(), v -> leaveFuture.complete(null));
setUpstreamFuture.get(5, SECONDS);
leaveFuture.get(5, SECONDS);
});
mController.setTestNetworkAsUpstreamAndWait(null);
mController.leaveAndWait();
}
@Test

View file

@ -16,31 +16,22 @@
package android.net.thread;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_DETACHED;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_STOPPED;
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.LEAVE_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.RESTART_JOIN_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.waitForStateAnyOf;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.annotation.Nullable;
import android.content.Context;
import android.net.thread.ThreadNetworkController.StateCallback;
import android.net.thread.utils.OtDaemonController;
import android.net.thread.utils.ThreadFeatureCheckerRule;
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.net.thread.utils.ThreadNetworkControllerWrapper;
import android.os.SystemClock;
import androidx.test.core.app.ApplicationProvider;
@ -55,7 +46,6 @@ import org.junit.runner.RunWith;
import java.net.Inet6Address;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/** Tests for E2E Android Thread integration with ot-daemon, ConnectivityService, etc.. */
@LargeTest
@ -76,17 +66,14 @@ public class ThreadIntegrationTest {
@Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
private ThreadNetworkController mController;
private final ThreadNetworkControllerWrapper mController =
ThreadNetworkControllerWrapper.newInstance(mContext);
private OtDaemonController mOtCtl;
@Before
public void setUp() throws Exception {
mController =
mContext.getSystemService(ThreadNetworkManager.class)
.getAllThreadNetworkControllers()
.get(0);
mOtCtl = new OtDaemonController();
leaveAndWait(mController);
mController.leaveAndWait();
// TODO: b/323301831 - This is a workaround to avoid unnecessary delay to re-form a network
mOtCtl.factoryReset();
@ -94,43 +81,43 @@ public class ThreadIntegrationTest {
@After
public void tearDown() throws Exception {
setTestUpStreamNetworkAndWait(mController, null);
leaveAndWait(mController);
mController.setTestNetworkAsUpstreamAndWait(null);
mController.leaveAndWait();
}
@Test
public void otDaemonRestart_notJoinedAndStopped_deviceRoleIsStopped() throws Exception {
leaveAndWait(mController);
mController.leaveAndWait();
runShellCommand("stop ot-daemon");
// TODO(b/323331973): the sleep is needed to workaround the race conditions
SystemClock.sleep(200);
waitForStateAnyOf(mController, List.of(DEVICE_ROLE_STOPPED), CALLBACK_TIMEOUT);
mController.waitForRole(DEVICE_ROLE_STOPPED, CALLBACK_TIMEOUT);
}
@Test
public void otDaemonRestart_JoinedNetworkAndStopped_autoRejoined() throws Exception {
joinAndWait(mController, DEFAULT_DATASET);
mController.joinAndWait(DEFAULT_DATASET);
runShellCommand("stop ot-daemon");
waitForStateAnyOf(mController, List.of(DEVICE_ROLE_DETACHED), CALLBACK_TIMEOUT);
waitForStateAnyOf(mController, List.of(DEVICE_ROLE_LEADER), RESTART_JOIN_TIMEOUT);
mController.waitForRole(DEVICE_ROLE_DETACHED, CALLBACK_TIMEOUT);
mController.waitForRole(DEVICE_ROLE_LEADER, RESTART_JOIN_TIMEOUT);
}
@Test
public void otDaemonFactoryReset_deviceRoleIsStopped() throws Exception {
joinAndWait(mController, DEFAULT_DATASET);
mController.joinAndWait(DEFAULT_DATASET);
mOtCtl.factoryReset();
assertThat(getDeviceRole(mController)).isEqualTo(DEVICE_ROLE_STOPPED);
assertThat(mController.getDeviceRole()).isEqualTo(DEVICE_ROLE_STOPPED);
}
@Test
public void otDaemonFactoryReset_addressesRemoved() throws Exception {
joinAndWait(mController, DEFAULT_DATASET);
mController.joinAndWait(DEFAULT_DATASET);
mOtCtl.factoryReset();
String ifconfig = runShellCommand("ifconfig thread-wpan");
@ -140,7 +127,7 @@ public class ThreadIntegrationTest {
@Test
public void tunInterface_joinedNetwork_otAddressesAddedToTunInterface() throws Exception {
joinAndWait(mController, DEFAULT_DATASET);
mController.joinAndWait(DEFAULT_DATASET);
String ifconfig = runShellCommand("ifconfig thread-wpan");
List<Inet6Address> otAddresses = mOtCtl.getAddresses();
@ -152,46 +139,4 @@ public class ThreadIntegrationTest {
// TODO (b/323300829): add more tests for integration with linux platform and
// ConnectivityService
private static int getDeviceRole(ThreadNetworkController controller) throws Exception {
CompletableFuture<Integer> future = new CompletableFuture<>();
StateCallback callback = future::complete;
controller.registerStateCallback(directExecutor(), callback);
try {
return future.get(CALLBACK_TIMEOUT.toMillis(), MILLISECONDS);
} finally {
controller.unregisterStateCallback(callback);
}
}
private static void joinAndWait(
ThreadNetworkController controller, ActiveOperationalDataset activeDataset)
throws Exception {
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
() -> controller.join(activeDataset, directExecutor(), result -> {}));
waitForStateAnyOf(controller, List.of(DEVICE_ROLE_LEADER), RESTART_JOIN_TIMEOUT);
}
private static void leaveAndWait(ThreadNetworkController controller) throws Exception {
CompletableFuture<Void> future = new CompletableFuture<>();
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
() -> controller.leave(directExecutor(), future::complete));
future.get(LEAVE_TIMEOUT.toMillis(), MILLISECONDS);
}
private static void setTestUpStreamNetworkAndWait(
ThreadNetworkController controller, @Nullable String networkInterfaceName)
throws Exception {
CompletableFuture<Void> future = new CompletableFuture<>();
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
NETWORK_SETTINGS,
() -> {
controller.setTestNetworkAsUpstream(
networkInterfaceName, directExecutor(), future::complete);
});
future.get(CALLBACK_TIMEOUT.toMillis(), MILLISECONDS);
}
}

View file

@ -0,0 +1,195 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.thread.utils;
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static java.util.concurrent.TimeUnit.SECONDS;
import android.annotation.Nullable;
import android.content.Context;
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.ThreadNetworkController;
import android.net.thread.ThreadNetworkController.StateCallback;
import android.net.thread.ThreadNetworkException;
import android.net.thread.ThreadNetworkManager;
import android.os.OutcomeReceiver;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
/** A helper class which provides synchronous API wrappers for {@link ThreadNetworkController}. */
public final class ThreadNetworkControllerWrapper {
public static final Duration JOIN_TIMEOUT = Duration.ofSeconds(10);
public static final Duration LEAVE_TIMEOUT = Duration.ofSeconds(2);
private static final Duration CALLBACK_TIMEOUT = Duration.ofSeconds(1);
private final ThreadNetworkController mController;
/**
* Returns a new {@link ThreadNetworkControllerWrapper} instance or {@code null} if Thread
* feature is not supported on this device.
*/
@Nullable
public static ThreadNetworkControllerWrapper newInstance(Context context) {
final ThreadNetworkManager manager = context.getSystemService(ThreadNetworkManager.class);
if (manager == null) {
return null;
}
return new ThreadNetworkControllerWrapper(manager.getAllThreadNetworkControllers().get(0));
}
private ThreadNetworkControllerWrapper(ThreadNetworkController controller) {
mController = controller;
}
/**
* Returns the Thread enabled state.
*
* <p>The value can be one of {@code ThreadNetworkController#STATE_*}.
*/
public final int getEnabledState()
throws InterruptedException, ExecutionException, TimeoutException {
CompletableFuture<Integer> future = new CompletableFuture<>();
StateCallback callback =
new StateCallback() {
@Override
public void onThreadEnableStateChanged(int enabledState) {
future.complete(enabledState);
}
@Override
public void onDeviceRoleChanged(int deviceRole) {}
};
runAsShell(
ACCESS_NETWORK_STATE,
() -> mController.registerStateCallback(directExecutor(), callback));
try {
return future.get(CALLBACK_TIMEOUT.toSeconds(), SECONDS);
} finally {
runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
}
}
/**
* Returns the Thread device role.
*
* <p>The value can be one of {@code ThreadNetworkController#DEVICE_ROLE_*}.
*/
public final int getDeviceRole()
throws InterruptedException, ExecutionException, TimeoutException {
CompletableFuture<Integer> future = new CompletableFuture<>();
StateCallback callback = future::complete;
runAsShell(
ACCESS_NETWORK_STATE,
() -> mController.registerStateCallback(directExecutor(), callback));
try {
return future.get(CALLBACK_TIMEOUT.toSeconds(), SECONDS);
} finally {
runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
}
}
/** Joins the given network and wait for this device to become attached. */
public void joinAndWait(ActiveOperationalDataset activeDataset)
throws InterruptedException, ExecutionException, TimeoutException {
CompletableFuture<Void> future = new CompletableFuture<>();
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
() ->
mController.join(
activeDataset, directExecutor(), newOutcomeReceiver(future)));
future.get(JOIN_TIMEOUT.toSeconds(), SECONDS);
}
/** An synchronous variant of {@link ThreadNetworkController#leave}. */
public void leaveAndWait() throws InterruptedException, ExecutionException, TimeoutException {
CompletableFuture<Void> future = new CompletableFuture<>();
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
() -> mController.leave(directExecutor(), future::complete));
future.get(LEAVE_TIMEOUT.toSeconds(), SECONDS);
}
/** Waits for the device role to become {@code deviceRole}. */
public int waitForRole(int deviceRole, Duration timeout)
throws InterruptedException, ExecutionException, TimeoutException {
return waitForRoleAnyOf(List.of(deviceRole), timeout);
}
/** Waits for the device role to become one of the values specified in {@code deviceRoles}. */
public int waitForRoleAnyOf(List<Integer> deviceRoles, Duration timeout)
throws InterruptedException, ExecutionException, TimeoutException {
CompletableFuture<Integer> future = new CompletableFuture<>();
ThreadNetworkController.StateCallback callback =
newRole -> {
if (deviceRoles.contains(newRole)) {
future.complete(newRole);
}
};
runAsShell(
ACCESS_NETWORK_STATE,
() -> mController.registerStateCallback(directExecutor(), callback));
try {
return future.get(timeout.toSeconds(), SECONDS);
} finally {
mController.unregisterStateCallback(callback);
}
}
/** An synchronous variant of {@link ThreadNetworkController#setTestNetworkAsUpstream}. */
public void setTestNetworkAsUpstreamAndWait(@Nullable String networkInterfaceName)
throws InterruptedException, ExecutionException, TimeoutException {
CompletableFuture<Void> future = new CompletableFuture<>();
runAsShell(
PERMISSION_THREAD_NETWORK_PRIVILEGED,
NETWORK_SETTINGS,
() -> {
mController.setTestNetworkAsUpstream(
networkInterfaceName, directExecutor(), future::complete);
});
future.get(CALLBACK_TIMEOUT.toSeconds(), SECONDS);
}
private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
CompletableFuture<V> future) {
return new OutcomeReceiver<V, ThreadNetworkException>() {
@Override
public void onResult(V result) {
future.complete(result);
}
@Override
public void onError(ThreadNetworkException e) {
future.completeExceptionally(e);
}
};
}
}