[Thread] add command to force stop ot-daemon
The Thread HAL supports only one client for a single Thread chip, so ot-daemon needs to be force stopped in VTS tests because the testapp needs to connect to the HAL service to perform tests. Bug: 328538612 Change-Id: I5288e5747ab3957050d0e85c1ea684c6ead6c87d
This commit is contained in:
parent
98eb360c0d
commit
82599d3e21
8 changed files with 356 additions and 52 deletions
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
package com.android.server.thread;
|
||||
|
||||
import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
|
||||
import static android.net.thread.ThreadNetworkException.ERROR_UNAVAILABLE;
|
||||
|
||||
import android.net.thread.ActiveOperationalDataset;
|
||||
import android.net.thread.IActiveOperationalDatasetReceiver;
|
||||
import android.net.thread.ThreadNetworkException;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
|
@ -73,6 +75,17 @@ final class ActiveOperationalDatasetReceiverWrapper {
|
|||
}
|
||||
}
|
||||
|
||||
public void onError(Throwable e) {
|
||||
if (e instanceof ThreadNetworkException) {
|
||||
ThreadNetworkException threadException = (ThreadNetworkException) e;
|
||||
onError(threadException.getErrorCode(), threadException.getMessage());
|
||||
} else if (e instanceof RemoteException) {
|
||||
onError(ERROR_INTERNAL_ERROR, "Thread stack error");
|
||||
} else {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onError(int errorCode, String errorMessage) {
|
||||
synchronized (sPendingReceiversLock) {
|
||||
sPendingReceivers.remove(this);
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@
|
|||
|
||||
package com.android.server.thread;
|
||||
|
||||
import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
|
||||
import static android.net.thread.ThreadNetworkException.ERROR_UNAVAILABLE;
|
||||
|
||||
import android.net.thread.IOperationReceiver;
|
||||
import android.net.thread.ThreadNetworkException;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
|
@ -29,6 +31,7 @@ import java.util.Set;
|
|||
/** A {@link IOperationReceiver} wrapper which makes it easier to invoke the callbacks. */
|
||||
final class OperationReceiverWrapper {
|
||||
private final IOperationReceiver mReceiver;
|
||||
private final boolean mExpectOtDaemonDied;
|
||||
|
||||
private static final Object sPendingReceiversLock = new Object();
|
||||
|
||||
|
|
@ -36,7 +39,19 @@ final class OperationReceiverWrapper {
|
|||
private static final Set<OperationReceiverWrapper> sPendingReceivers = new HashSet<>();
|
||||
|
||||
public OperationReceiverWrapper(IOperationReceiver receiver) {
|
||||
this.mReceiver = receiver;
|
||||
this(receiver, false /* expectOtDaemonDied */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link OperationReceiverWrapper}.
|
||||
*
|
||||
* <p>If {@code expectOtDaemonDied} is {@code true}, it's expected that ot-daemon becomes dead
|
||||
* before {@code receiver} is completed with {@code onSuccess} and {@code onError} and {@code
|
||||
* receiver#onSuccess} will be invoked in this case.
|
||||
*/
|
||||
public OperationReceiverWrapper(IOperationReceiver receiver, boolean expectOtDaemonDied) {
|
||||
mReceiver = receiver;
|
||||
mExpectOtDaemonDied = expectOtDaemonDied;
|
||||
|
||||
synchronized (sPendingReceiversLock) {
|
||||
sPendingReceivers.add(this);
|
||||
|
|
@ -47,7 +62,11 @@ final class OperationReceiverWrapper {
|
|||
synchronized (sPendingReceiversLock) {
|
||||
for (OperationReceiverWrapper receiver : sPendingReceivers) {
|
||||
try {
|
||||
receiver.mReceiver.onError(ERROR_UNAVAILABLE, "Thread daemon died");
|
||||
if (receiver.mExpectOtDaemonDied) {
|
||||
receiver.mReceiver.onSuccess();
|
||||
} else {
|
||||
receiver.mReceiver.onError(ERROR_UNAVAILABLE, "Thread daemon died");
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// The client is dead, do nothing
|
||||
}
|
||||
|
|
@ -68,6 +87,17 @@ final class OperationReceiverWrapper {
|
|||
}
|
||||
}
|
||||
|
||||
public void onError(Throwable e) {
|
||||
if (e instanceof ThreadNetworkException) {
|
||||
ThreadNetworkException threadException = (ThreadNetworkException) e;
|
||||
onError(threadException.getErrorCode(), threadException.getMessage());
|
||||
} else if (e instanceof RemoteException) {
|
||||
onError(ERROR_INTERNAL_ERROR, "Thread stack error");
|
||||
} else {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onError(int errorCode, String errorMessage, Object... messageArgs) {
|
||||
synchronized (sPendingReceiversLock) {
|
||||
sPendingReceivers.remove(this);
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ import android.net.thread.OperationalDatasetTimestamp;
|
|||
import android.net.thread.PendingOperationalDataset;
|
||||
import android.net.thread.ThreadNetworkController;
|
||||
import android.net.thread.ThreadNetworkController.DeviceRole;
|
||||
import android.net.thread.ThreadNetworkException;
|
||||
import android.net.thread.ThreadNetworkException.ErrorCode;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
|
|
@ -193,6 +194,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
private final ThreadPersistentSettings mPersistentSettings;
|
||||
private final UserManager mUserManager;
|
||||
private boolean mUserRestricted;
|
||||
private boolean mForceStopOtDaemonEnabled;
|
||||
|
||||
private BorderRouterConfigurationParcel mBorderRouterConfig;
|
||||
|
||||
|
|
@ -311,13 +313,21 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
try {
|
||||
getOtDaemon();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to initialize ot-daemon");
|
||||
Log.e(TAG, "Failed to initialize ot-daemon", e);
|
||||
} catch (ThreadNetworkException e) {
|
||||
// no ThreadNetworkException.ERROR_THREAD_DISABLED error should be thrown
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private IOtDaemon getOtDaemon() throws RemoteException {
|
||||
private IOtDaemon getOtDaemon() throws RemoteException, ThreadNetworkException {
|
||||
checkOnHandlerThread();
|
||||
|
||||
if (mForceStopOtDaemonEnabled) {
|
||||
throw new ThreadNetworkException(
|
||||
ERROR_THREAD_DISABLED, "ot-daemon is forcibly stopped");
|
||||
}
|
||||
|
||||
if (mOtDaemon != null) {
|
||||
return mOtDaemon;
|
||||
}
|
||||
|
|
@ -406,6 +416,55 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Force stops ot-daemon immediately and prevents ot-daemon from being restarted by
|
||||
* system_server again.
|
||||
*
|
||||
* <p>This is for VTS testing only.
|
||||
*/
|
||||
@RequiresPermission(PERMISSION_THREAD_NETWORK_PRIVILEGED)
|
||||
void forceStopOtDaemonForTest(boolean enabled, @NonNull IOperationReceiver receiver) {
|
||||
enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
|
||||
|
||||
mHandler.post(
|
||||
() ->
|
||||
forceStopOtDaemonForTestInternal(
|
||||
enabled,
|
||||
new OperationReceiverWrapper(
|
||||
receiver, true /* expectOtDaemonDied */)));
|
||||
}
|
||||
|
||||
private void forceStopOtDaemonForTestInternal(
|
||||
boolean enabled, @NonNull OperationReceiverWrapper receiver) {
|
||||
checkOnHandlerThread();
|
||||
if (enabled == mForceStopOtDaemonEnabled) {
|
||||
receiver.onSuccess();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
mForceStopOtDaemonEnabled = false;
|
||||
maybeInitializeOtDaemon();
|
||||
receiver.onSuccess();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
getOtDaemon().terminate();
|
||||
// Do not invoke the {@code receiver} callback here but wait for ot-daemon to
|
||||
// become dead, so that it's guaranteed that ot-daemon is stopped when {@code
|
||||
// receiver} is completed
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "otDaemon.terminate failed", e);
|
||||
receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
|
||||
} catch (ThreadNetworkException e) {
|
||||
// No ThreadNetworkException.ERROR_THREAD_DISABLED error will be thrown
|
||||
throw new AssertionError(e);
|
||||
} finally {
|
||||
mForceStopOtDaemonEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void setEnabled(boolean isEnabled, @NonNull IOperationReceiver receiver) {
|
||||
enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
|
||||
|
||||
|
|
@ -435,9 +494,9 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
|
||||
try {
|
||||
getOtDaemon().setThreadEnabled(isEnabled, newOtStatusReceiver(receiver));
|
||||
} catch (RemoteException e) {
|
||||
} catch (RemoteException | ThreadNetworkException e) {
|
||||
Log.e(TAG, "otDaemon.setThreadEnabled failed", e);
|
||||
receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
|
||||
receiver.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -494,7 +553,9 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
|
||||
/** Returns {@code true} if Thread is set enabled. */
|
||||
private boolean isEnabled() {
|
||||
return !mUserRestricted && mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
|
||||
return !mForceStopOtDaemonEnabled
|
||||
&& !mUserRestricted
|
||||
&& mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
|
||||
}
|
||||
|
||||
/** Returns {@code true} if Thread has been restricted for the user. */
|
||||
|
|
@ -682,9 +743,9 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
|
||||
try {
|
||||
getOtDaemon().getChannelMasks(newChannelMasksReceiver(networkName, receiver));
|
||||
} catch (RemoteException e) {
|
||||
} catch (RemoteException | ThreadNetworkException e) {
|
||||
Log.e(TAG, "otDaemon.getChannelMasks failed", e);
|
||||
receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
|
||||
receiver.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -894,9 +955,9 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
try {
|
||||
// The otDaemon.join() will leave first if this device is currently attached
|
||||
getOtDaemon().join(activeDataset.toThreadTlvs(), newOtStatusReceiver(receiver));
|
||||
} catch (RemoteException e) {
|
||||
} catch (RemoteException | ThreadNetworkException e) {
|
||||
Log.e(TAG, "otDaemon.join failed", e);
|
||||
receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
|
||||
receiver.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -919,9 +980,9 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
getOtDaemon()
|
||||
.scheduleMigration(
|
||||
pendingDataset.toThreadTlvs(), newOtStatusReceiver(receiver));
|
||||
} catch (RemoteException e) {
|
||||
} catch (RemoteException | ThreadNetworkException e) {
|
||||
Log.e(TAG, "otDaemon.scheduleMigration failed", e);
|
||||
receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
|
||||
receiver.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -937,9 +998,9 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
|
||||
try {
|
||||
getOtDaemon().leave(newOtStatusReceiver(receiver));
|
||||
} catch (RemoteException e) {
|
||||
// Oneway AIDL API should never throw?
|
||||
receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
|
||||
} catch (RemoteException | ThreadNetworkException e) {
|
||||
Log.e(TAG, "otDaemon.leave failed", e);
|
||||
receiver.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -970,9 +1031,9 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
|
||||
try {
|
||||
getOtDaemon().setCountryCode(countryCode, newOtStatusReceiver(receiver));
|
||||
} catch (RemoteException e) {
|
||||
} catch (RemoteException | ThreadNetworkException e) {
|
||||
Log.e(TAG, "otDaemon.setCountryCode failed", e);
|
||||
receiver.onError(ERROR_INTERNAL_ERROR, "Thread stack error");
|
||||
receiver.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1203,8 +1264,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
|
||||
try {
|
||||
getOtDaemon().registerStateCallback(this, callbackMetadata.id);
|
||||
} catch (RemoteException e) {
|
||||
// oneway operation should never fail
|
||||
} catch (RemoteException | ThreadNetworkException e) {
|
||||
Log.e(TAG, "otDaemon.registerStateCallback failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1244,8 +1305,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
|
||||
try {
|
||||
getOtDaemon().registerStateCallback(this, callbackMetadata.id);
|
||||
} catch (RemoteException e) {
|
||||
// oneway operation should never fail
|
||||
} catch (RemoteException | ThreadNetworkException e) {
|
||||
Log.e(TAG, "otDaemon.registerStateCallback failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import java.util.concurrent.TimeoutException;
|
|||
*/
|
||||
public class ThreadNetworkShellCommand extends BasicShellCommandHandler {
|
||||
private static final Duration SET_ENABLED_TIMEOUT = Duration.ofSeconds(2);
|
||||
private static final Duration FORCE_STOP_TIMEOUT = Duration.ofSeconds(1);
|
||||
|
||||
// These don't require root access.
|
||||
private static final List<String> NON_PRIVILEGED_COMMANDS =
|
||||
|
|
@ -108,6 +109,8 @@ public class ThreadNetworkShellCommand extends BasicShellCommandHandler {
|
|||
return setThreadEnabled(true);
|
||||
case "disable":
|
||||
return setThreadEnabled(false);
|
||||
case "force-stop-ot-daemon":
|
||||
return forceStopOtDaemon();
|
||||
case "force-country-code":
|
||||
boolean enabled;
|
||||
try {
|
||||
|
|
@ -143,34 +146,58 @@ public class ThreadNetworkShellCommand extends BasicShellCommandHandler {
|
|||
}
|
||||
|
||||
private int setThreadEnabled(boolean enabled) {
|
||||
final PrintWriter perr = getErrorWriter();
|
||||
|
||||
CompletableFuture<Void> setEnabledFuture = new CompletableFuture<>();
|
||||
mControllerService.setEnabled(
|
||||
enabled,
|
||||
new IOperationReceiver.Stub() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
setEnabledFuture.complete(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String errorMessage) {
|
||||
setEnabledFuture.completeExceptionally(
|
||||
new ThreadNetworkException(errorCode, errorMessage));
|
||||
}
|
||||
});
|
||||
mControllerService.setEnabled(enabled, newOperationReceiver(setEnabledFuture));
|
||||
return waitForFuture(setEnabledFuture, FORCE_STOP_TIMEOUT, getErrorWriter());
|
||||
}
|
||||
|
||||
private int forceStopOtDaemon() {
|
||||
final PrintWriter errorWriter = getErrorWriter();
|
||||
boolean enabled;
|
||||
try {
|
||||
setEnabledFuture.get(SET_ENABLED_TIMEOUT.toSeconds(), TimeUnit.SECONDS);
|
||||
enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
|
||||
} catch (IllegalArgumentException e) {
|
||||
errorWriter.println("Invalid argument: " + e.getMessage());
|
||||
return -1;
|
||||
}
|
||||
|
||||
CompletableFuture<Void> forceStopFuture = new CompletableFuture<>();
|
||||
mControllerService.forceStopOtDaemonForTest(enabled, newOperationReceiver(forceStopFuture));
|
||||
return waitForFuture(forceStopFuture, FORCE_STOP_TIMEOUT, getErrorWriter());
|
||||
}
|
||||
|
||||
private static IOperationReceiver newOperationReceiver(CompletableFuture<Void> future) {
|
||||
return new IOperationReceiver.Stub() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
future.complete(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String errorMessage) {
|
||||
future.completeExceptionally(new ThreadNetworkException(errorCode, errorMessage));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the future to complete within given timeout.
|
||||
*
|
||||
* <p>Returns 0 if {@code future} completed successfully, or -1 if {@code future} failed to
|
||||
* complete. When failed, error messages are printed to {@code errorWriter}.
|
||||
*/
|
||||
private int waitForFuture(
|
||||
CompletableFuture<Void> future, Duration timeout, PrintWriter errorWriter) {
|
||||
try {
|
||||
future.get(timeout.toSeconds(), TimeUnit.SECONDS);
|
||||
return 0;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
perr.println("Failed: " + e.getMessage());
|
||||
errorWriter.println("Failed: " + e.getMessage());
|
||||
} catch (ExecutionException e) {
|
||||
perr.println("Failed: " + e.getCause().getMessage());
|
||||
errorWriter.println("Failed: " + e.getCause().getMessage());
|
||||
} catch (TimeoutException e) {
|
||||
perr.println("Failed: command timeout for " + SET_ENABLED_TIMEOUT);
|
||||
errorWriter.println("Failed: command timeout for " + timeout);
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
|
@ -210,6 +237,8 @@ public class ThreadNetworkShellCommand extends BasicShellCommandHandler {
|
|||
private void onHelpPrivileged(PrintWriter pw) {
|
||||
pw.println(" force-country-code enabled <two-letter code> | disabled ");
|
||||
pw.println(" Sets country code to <two-letter code> or left for normal value");
|
||||
pw.println(" force-stop-ot-daemon enabled | disabled ");
|
||||
pw.println(" force stop ot-daemon service");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -18,11 +18,14 @@ package android.net.thread;
|
|||
|
||||
import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
|
||||
import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
|
||||
import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
|
||||
|
||||
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
|
||||
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.thread.utils.ThreadFeatureCheckerRule;
|
||||
import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
|
||||
|
|
@ -32,10 +35,14 @@ import androidx.test.core.app.ApplicationProvider;
|
|||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/** Integration tests for {@link ThreadNetworkShellCommand}. */
|
||||
@LargeTest
|
||||
@RequiresThreadFeature
|
||||
|
|
@ -47,6 +54,21 @@ public class ThreadNetworkShellCommandTest {
|
|||
private final ThreadNetworkControllerWrapper mController =
|
||||
ThreadNetworkControllerWrapper.newInstance(mContext);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
ensureThreadEnabled();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
ensureThreadEnabled();
|
||||
}
|
||||
|
||||
private static void ensureThreadEnabled() {
|
||||
runThreadCommand("force-stop-ot-daemon disabled");
|
||||
runThreadCommand("enable");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enable_threadStateIsEnabled() throws Exception {
|
||||
runThreadCommand("enable");
|
||||
|
|
@ -61,6 +83,38 @@ public class ThreadNetworkShellCommandTest {
|
|||
assertThat(mController.getEnabledState()).isEqualTo(STATE_DISABLED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceStopOtDaemon_forceStopEnabled_otDaemonServiceDisappear() {
|
||||
runThreadCommand("force-stop-ot-daemon enabled");
|
||||
|
||||
assertThat(runShellCommandOrThrow("service list")).doesNotContain("ot_daemon");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceStopOtDaemon_forceStopEnabled_canNotEnableThread() throws Exception {
|
||||
runThreadCommand("force-stop-ot-daemon enabled");
|
||||
|
||||
ExecutionException thrown =
|
||||
assertThrows(ExecutionException.class, () -> mController.setEnabledAndWait(true));
|
||||
ThreadNetworkException cause = (ThreadNetworkException) thrown.getCause();
|
||||
assertThat(cause.getErrorCode()).isEqualTo(ERROR_THREAD_DISABLED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceStopOtDaemon_forceStopDisabled_otDaemonServiceAppears() throws Exception {
|
||||
runThreadCommand("force-stop-ot-daemon disabled");
|
||||
|
||||
assertThat(runShellCommandOrThrow("service list")).contains("ot_daemon");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceStopOtDaemon_forceStopDisabled_canEnableThread() throws Exception {
|
||||
runThreadCommand("force-stop-ot-daemon disabled");
|
||||
|
||||
mController.setEnabledAndWait(true);
|
||||
assertThat(mController.getEnabledState()).isEqualTo(STATE_ENABLED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceCountryCode_setCN_getCountryCodeReturnsCN() {
|
||||
runThreadCommand("force-country-code enabled CN");
|
||||
|
|
@ -70,6 +124,6 @@ public class ThreadNetworkShellCommandTest {
|
|||
}
|
||||
|
||||
private static String runThreadCommand(String cmd) {
|
||||
return runShellCommand("cmd thread_network " + cmd);
|
||||
return runShellCommandOrThrow("cmd thread_network " + cmd);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ 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 static final Duration SET_ENABLED_TIMEOUT = Duration.ofSeconds(2);
|
||||
|
||||
private final ThreadNetworkController mController;
|
||||
|
||||
|
|
@ -115,6 +116,18 @@ public final class ThreadNetworkControllerWrapper {
|
|||
}
|
||||
}
|
||||
|
||||
/** An synchronous variant of {@link ThreadNetworkController#setEnabled}. */
|
||||
public void setEnabledAndWait(boolean enabled)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
runAsShell(
|
||||
PERMISSION_THREAD_NETWORK_PRIVILEGED,
|
||||
() ->
|
||||
mController.setEnabled(
|
||||
enabled, directExecutor(), newOutcomeReceiver(future)));
|
||||
future.get(SET_ENABLED_TIMEOUT.toSeconds(), SECONDS);
|
||||
}
|
||||
|
||||
/** Joins the given network and wait for this device to become attached. */
|
||||
public void joinAndWait(ActiveOperationalDataset activeDataset)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
|
|||
import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
|
||||
import static android.net.thread.ThreadNetworkException.ERROR_FAILED_PRECONDITION;
|
||||
import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
|
||||
import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
|
||||
import static android.net.thread.ThreadNetworkManager.DISALLOW_THREAD_NETWORK;
|
||||
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
|
||||
|
||||
|
|
@ -37,6 +38,7 @@ import static org.mockito.ArgumentMatchers.eq;
|
|||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
|
@ -440,4 +442,51 @@ public final class ThreadNetworkControllerServiceTest {
|
|||
verify(mockReceiver, never()).onSuccess(any(ActiveOperationalDataset.class));
|
||||
verify(mockReceiver, times(1)).onError(eq(ERROR_INTERNAL_ERROR), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceStopOtDaemonForTest_noPermission_throwsSecurityException() {
|
||||
doThrow(new SecurityException(""))
|
||||
.when(mContext)
|
||||
.enforceCallingOrSelfPermission(eq(PERMISSION_THREAD_NETWORK_PRIVILEGED), any());
|
||||
|
||||
assertThrows(
|
||||
SecurityException.class,
|
||||
() -> mService.forceStopOtDaemonForTest(true, new IOperationReceiver.Default()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceStopOtDaemonForTest_enabled_otDaemonDiesAndJoinFails() throws Exception {
|
||||
mService.initialize();
|
||||
IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
|
||||
IOperationReceiver mockJoinReceiver = mock(IOperationReceiver.class);
|
||||
|
||||
mService.forceStopOtDaemonForTest(true, mockReceiver);
|
||||
mTestLooper.dispatchAll();
|
||||
mService.join(DEFAULT_ACTIVE_DATASET, mockJoinReceiver);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mockReceiver, times(1)).onSuccess();
|
||||
assertThat(mFakeOtDaemon.isInitialized()).isFalse();
|
||||
verify(mockJoinReceiver, times(1)).onError(eq(ERROR_THREAD_DISABLED), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceStopOtDaemonForTest_disable_otDaemonRestartsAndJoinSccess() throws Exception {
|
||||
mService.initialize();
|
||||
IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
|
||||
IOperationReceiver mockJoinReceiver = mock(IOperationReceiver.class);
|
||||
|
||||
mService.forceStopOtDaemonForTest(true, mock(IOperationReceiver.class));
|
||||
mTestLooper.dispatchAll();
|
||||
mService.forceStopOtDaemonForTest(false, mockReceiver);
|
||||
mTestLooper.dispatchAll();
|
||||
mService.join(DEFAULT_ACTIVE_DATASET, mockJoinReceiver);
|
||||
mTestLooper.dispatchAll();
|
||||
mTestLooper.moveTimeForward(FakeOtDaemon.JOIN_DELAY.toMillis() + 100);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mockReceiver, times(1)).onSuccess();
|
||||
assertThat(mFakeOtDaemon.isInitialized()).isTrue();
|
||||
verify(mockJoinReceiver, times(1)).onSuccess();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,10 +16,15 @@
|
|||
|
||||
package com.android.server.thread;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.contains;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.validateMockitoUsage;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
|
@ -50,15 +55,14 @@ public class ThreadNetworkShellCommandTest {
|
|||
@Mock PrintWriter mErrorWriter;
|
||||
@Mock PrintWriter mOutputWriter;
|
||||
|
||||
ThreadNetworkShellCommand mThreadNetworkShellCommand;
|
||||
ThreadNetworkShellCommand mShellCommand;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mThreadNetworkShellCommand =
|
||||
new ThreadNetworkShellCommand(mControllerService, mCountryCode);
|
||||
mThreadNetworkShellCommand.setPrintWriters(mOutputWriter, mErrorWriter);
|
||||
mShellCommand = new ThreadNetworkShellCommand(mControllerService, mCountryCode);
|
||||
mShellCommand.setPrintWriters(mOutputWriter, mErrorWriter);
|
||||
}
|
||||
|
||||
@After
|
||||
|
|
@ -71,7 +75,7 @@ public class ThreadNetworkShellCommandTest {
|
|||
BinderUtil.setUid(Process.SHELL_UID);
|
||||
when(mCountryCode.getCountryCode()).thenReturn("US");
|
||||
|
||||
mThreadNetworkShellCommand.exec(
|
||||
mShellCommand.exec(
|
||||
new Binder(),
|
||||
new FileDescriptor(),
|
||||
new FileDescriptor(),
|
||||
|
|
@ -85,7 +89,7 @@ public class ThreadNetworkShellCommandTest {
|
|||
public void forceSetCountryCodeEnabled_executeInUnrootedShell_notAllowed() {
|
||||
BinderUtil.setUid(Process.SHELL_UID);
|
||||
|
||||
mThreadNetworkShellCommand.exec(
|
||||
mShellCommand.exec(
|
||||
new Binder(),
|
||||
new FileDescriptor(),
|
||||
new FileDescriptor(),
|
||||
|
|
@ -100,7 +104,7 @@ public class ThreadNetworkShellCommandTest {
|
|||
public void forceSetCountryCodeEnabled_executeInRootedShell_allowed() {
|
||||
BinderUtil.setUid(Process.ROOT_UID);
|
||||
|
||||
mThreadNetworkShellCommand.exec(
|
||||
mShellCommand.exec(
|
||||
new Binder(),
|
||||
new FileDescriptor(),
|
||||
new FileDescriptor(),
|
||||
|
|
@ -114,7 +118,7 @@ public class ThreadNetworkShellCommandTest {
|
|||
public void forceSetCountryCodeDisabled_executeInUnrootedShell_notAllowed() {
|
||||
BinderUtil.setUid(Process.SHELL_UID);
|
||||
|
||||
mThreadNetworkShellCommand.exec(
|
||||
mShellCommand.exec(
|
||||
new Binder(),
|
||||
new FileDescriptor(),
|
||||
new FileDescriptor(),
|
||||
|
|
@ -129,7 +133,7 @@ public class ThreadNetworkShellCommandTest {
|
|||
public void forceSetCountryCodeDisabled_executeInRootedShell_allowed() {
|
||||
BinderUtil.setUid(Process.ROOT_UID);
|
||||
|
||||
mThreadNetworkShellCommand.exec(
|
||||
mShellCommand.exec(
|
||||
new Binder(),
|
||||
new FileDescriptor(),
|
||||
new FileDescriptor(),
|
||||
|
|
@ -138,4 +142,55 @@ public class ThreadNetworkShellCommandTest {
|
|||
|
||||
verify(mCountryCode).clearOverrideCountryCode();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceStopOtDaemon_executeInUnrootedShell_failedAndServiceApiNotCalled() {
|
||||
BinderUtil.setUid(Process.SHELL_UID);
|
||||
|
||||
mShellCommand.exec(
|
||||
new Binder(),
|
||||
new FileDescriptor(),
|
||||
new FileDescriptor(),
|
||||
new FileDescriptor(),
|
||||
new String[] {"force-stop-ot-daemon", "enabled"});
|
||||
|
||||
verify(mControllerService, never()).forceStopOtDaemonForTest(anyBoolean(), any());
|
||||
verify(mErrorWriter, atLeastOnce()).println(contains("force-stop-ot-daemon"));
|
||||
verify(mOutputWriter, never()).println();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceStopOtDaemon_serviceThrows_failed() {
|
||||
BinderUtil.setUid(Process.ROOT_UID);
|
||||
doThrow(new SecurityException(""))
|
||||
.when(mControllerService)
|
||||
.forceStopOtDaemonForTest(eq(true), any());
|
||||
|
||||
mShellCommand.exec(
|
||||
new Binder(),
|
||||
new FileDescriptor(),
|
||||
new FileDescriptor(),
|
||||
new FileDescriptor(),
|
||||
new String[] {"force-stop-ot-daemon", "enabled"});
|
||||
|
||||
verify(mControllerService, times(1)).forceStopOtDaemonForTest(eq(true), any());
|
||||
verify(mOutputWriter, never()).println();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceStopOtDaemon_serviceApiTimeout_failedWithTimeoutError() {
|
||||
BinderUtil.setUid(Process.ROOT_UID);
|
||||
doNothing().when(mControllerService).forceStopOtDaemonForTest(eq(true), any());
|
||||
|
||||
mShellCommand.exec(
|
||||
new Binder(),
|
||||
new FileDescriptor(),
|
||||
new FileDescriptor(),
|
||||
new FileDescriptor(),
|
||||
new String[] {"force-stop-ot-daemon", "enabled"});
|
||||
|
||||
verify(mControllerService, times(1)).forceStopOtDaemonForTest(eq(true), any());
|
||||
verify(mErrorWriter, atLeastOnce()).println(contains("timeout"));
|
||||
verify(mOutputWriter, never()).println();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue