blob: dd2d8f96939e30dc4f7f8c2a9d369eac75c5b2ab [file] [log] [blame]
/*
* Copyright (C) 2020 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 com.android.bedstead.harrier;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
import static android.app.role.RoleManager.ROLE_BROWSER;
import static android.content.pm.PackageManager.FEATURE_MANAGED_USERS;
import static android.os.Build.VERSION.SDK_INT;
import static com.android.bedstead.harrier.AnnotationExecutorUtil.checkFailOrSkip;
import static com.android.bedstead.harrier.AnnotationExecutorUtil.failOrSkip;
import static com.android.bedstead.harrier.Defaults.DEFAULT_PASSWORD;
import static com.android.bedstead.harrier.annotations.EnsureHasAccount.DEFAULT_ACCOUNT_KEY;
import static com.android.bedstead.harrier.annotations.EnsureTestAppInstalled.DEFAULT_KEY;
import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDelegate.DELEGATE_KEY;
import static com.android.bedstead.nene.flags.CommonFlags.DevicePolicyManager.ENABLE_DEVICE_POLICY_ENGINE_FLAG;
import static com.android.bedstead.nene.flags.CommonFlags.DevicePolicyManager.PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG;
import static com.android.bedstead.nene.flags.CommonFlags.NAMESPACE_DEVICE_POLICY_MANAGER;
import static com.android.bedstead.nene.userrestrictions.CommonUserRestrictions.DISALLOW_ADD_CLONE_PROFILE;
import static com.android.bedstead.nene.userrestrictions.CommonUserRestrictions.DISALLOW_ADD_MANAGED_PROFILE;
import static com.android.bedstead.nene.userrestrictions.CommonUserRestrictions.DISALLOW_ADD_PRIVATE_PROFILE;
import static com.android.bedstead.nene.userrestrictions.CommonUserRestrictions.DISALLOW_ADD_USER;
import static com.android.bedstead.nene.userrestrictions.CommonUserRestrictions.DISALLOW_BLUETOOTH;
import static com.android.bedstead.nene.users.UserType.MANAGED_PROFILE_TYPE_NAME;
import static com.android.bedstead.nene.users.UserType.SECONDARY_USER_TYPE_NAME;
import static com.android.bedstead.nene.utils.Versions.T;
import static com.android.bedstead.nene.utils.Versions.meetsSdkVersionRequirements;
import static com.android.bedstead.remoteaccountauthenticator.RemoteAccountAuthenticator.REMOTE_ACCOUNT_AUTHENTICATOR_TEST_APP;
import static org.junit.Assert.assertFalse;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.UserManager;
import android.service.quicksettings.TileService;
import android.util.Log;
import androidx.annotation.Nullable;
import com.android.bedstead.harrier.annotations.AfterClass;
import com.android.bedstead.harrier.annotations.BeforeClass;
import com.android.bedstead.harrier.annotations.EnsureBluetoothDisabled;
import com.android.bedstead.harrier.annotations.EnsureBluetoothEnabled;
import com.android.bedstead.harrier.annotations.EnsureCanAddUser;
import com.android.bedstead.harrier.annotations.EnsureCanGetPermission;
import com.android.bedstead.harrier.annotations.EnsureDefaultContentSuggestionsServiceDisabled;
import com.android.bedstead.harrier.annotations.EnsureDefaultContentSuggestionsServiceEnabled;
import com.android.bedstead.harrier.annotations.EnsureDoesNotHaveAppOp;
import com.android.bedstead.harrier.annotations.EnsureDoesNotHavePermission;
import com.android.bedstead.harrier.annotations.EnsureDoesNotHaveUserRestriction;
import com.android.bedstead.harrier.annotations.EnsureFeatureFlagEnabled;
import com.android.bedstead.harrier.annotations.EnsureFeatureFlagNotEnabled;
import com.android.bedstead.harrier.annotations.EnsureFeatureFlagValue;
import com.android.bedstead.harrier.annotations.EnsureGlobalSettingSet;
import com.android.bedstead.harrier.annotations.EnsureHasAccount;
import com.android.bedstead.harrier.annotations.EnsureHasAccountAuthenticator;
import com.android.bedstead.harrier.annotations.EnsureHasAccounts;
import com.android.bedstead.harrier.annotations.EnsureHasAdditionalUser;
import com.android.bedstead.harrier.annotations.EnsureHasAppOp;
import com.android.bedstead.harrier.annotations.EnsureHasNoAccounts;
import com.android.bedstead.harrier.annotations.EnsureHasNoAdditionalUser;
import com.android.bedstead.harrier.annotations.EnsureHasPermission;
import com.android.bedstead.harrier.annotations.EnsureHasTestContentSuggestionsService;
import com.android.bedstead.harrier.annotations.EnsureHasUserRestriction;
import com.android.bedstead.harrier.annotations.EnsurePackageNotInstalled;
import com.android.bedstead.harrier.annotations.EnsurePasswordNotSet;
import com.android.bedstead.harrier.annotations.EnsurePasswordSet;
import com.android.bedstead.harrier.annotations.EnsurePolicyOperationUnsafe;
import com.android.bedstead.harrier.annotations.EnsurePropertySet;
import com.android.bedstead.harrier.annotations.EnsureScreenIsOn;
import com.android.bedstead.harrier.annotations.EnsureSecureSettingSet;
import com.android.bedstead.harrier.annotations.EnsureTestAppDoesNotHavePermission;
import com.android.bedstead.harrier.annotations.EnsureTestAppHasAppOp;
import com.android.bedstead.harrier.annotations.EnsureTestAppHasPermission;
import com.android.bedstead.harrier.annotations.EnsureTestAppInstalled;
import com.android.bedstead.harrier.annotations.EnsureUnlocked;
import com.android.bedstead.harrier.annotations.EnsureWifiDisabled;
import com.android.bedstead.harrier.annotations.EnsureWifiEnabled;
import com.android.bedstead.harrier.annotations.FailureMode;
import com.android.bedstead.harrier.annotations.OtherUser;
import com.android.bedstead.harrier.annotations.RequireAdbOverWifi;
import com.android.bedstead.harrier.annotations.RequireAdbRoot;
import com.android.bedstead.harrier.annotations.RequireDoesNotHaveFeature;
import com.android.bedstead.harrier.annotations.RequireFactoryResetProtectionPolicySupported;
import com.android.bedstead.harrier.annotations.RequireFeature;
import com.android.bedstead.harrier.annotations.RequireFeatureFlagEnabled;
import com.android.bedstead.harrier.annotations.RequireFeatureFlagNotEnabled;
import com.android.bedstead.harrier.annotations.RequireFeatureFlagValue;
import com.android.bedstead.harrier.annotations.RequireHasDefaultBrowser;
import com.android.bedstead.harrier.annotations.RequireHeadlessSystemUserMode;
import com.android.bedstead.harrier.annotations.RequireInstantApp;
import com.android.bedstead.harrier.annotations.RequireLowRamDevice;
import com.android.bedstead.harrier.annotations.RequireMultiUserSupport;
import com.android.bedstead.harrier.annotations.RequireNotHeadlessSystemUserMode;
import com.android.bedstead.harrier.annotations.RequireNotInstantApp;
import com.android.bedstead.harrier.annotations.RequireNotLowRamDevice;
import com.android.bedstead.harrier.annotations.RequireNotVisibleBackgroundUsers;
import com.android.bedstead.harrier.annotations.RequireNotVisibleBackgroundUsersOnDefaultDisplay;
import com.android.bedstead.harrier.annotations.RequirePackageInstalled;
import com.android.bedstead.harrier.annotations.RequirePackageNotInstalled;
import com.android.bedstead.harrier.annotations.RequireQuickSettingsSupport;
import com.android.bedstead.harrier.annotations.RequireRunNotOnVisibleBackgroundNonProfileUser;
import com.android.bedstead.harrier.annotations.RequireRunOnAdditionalUser;
import com.android.bedstead.harrier.annotations.RequireRunOnVisibleBackgroundNonProfileUser;
import com.android.bedstead.harrier.annotations.RequireSdkVersion;
import com.android.bedstead.harrier.annotations.RequireStorageEncryptionSupported;
import com.android.bedstead.harrier.annotations.RequireStorageEncryptionUnsupported;
import com.android.bedstead.harrier.annotations.RequireSystemServiceAvailable;
import com.android.bedstead.harrier.annotations.RequireTargetSdkVersion;
import com.android.bedstead.harrier.annotations.RequireTelephonySupport;
import com.android.bedstead.harrier.annotations.RequireUserSupported;
import com.android.bedstead.harrier.annotations.RequireVisibleBackgroundUsers;
import com.android.bedstead.harrier.annotations.RequireVisibleBackgroundUsersOnDefaultDisplay;
import com.android.bedstead.harrier.annotations.TestTag;
import com.android.bedstead.harrier.annotations.UsesAnnotationExecutor;
import com.android.bedstead.harrier.annotations.enterprise.AdditionalQueryParameters;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDelegate;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDevicePolicyManagerRoleHolder;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDelegate;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoProfileOwner;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwnerKt;
import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
import com.android.bedstead.harrier.annotations.enterprise.MostImportantCoexistenceTest;
import com.android.bedstead.harrier.annotations.enterprise.MostRestrictiveCoexistenceTest;
import com.android.bedstead.harrier.annotations.enterprise.RequireHasPolicyExemptApps;
import com.android.bedstead.harrier.annotations.meta.EnsureHasNoProfileAnnotation;
import com.android.bedstead.harrier.annotations.meta.EnsureHasNoUserAnnotation;
import com.android.bedstead.harrier.annotations.meta.EnsureHasProfileAnnotation;
import com.android.bedstead.harrier.annotations.meta.EnsureHasUserAnnotation;
import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
import com.android.bedstead.harrier.annotations.meta.RequireRunOnProfileAnnotation;
import com.android.bedstead.harrier.annotations.meta.RequireRunOnUserAnnotation;
import com.android.bedstead.harrier.annotations.meta.RequiresBedsteadJUnit4;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.accounts.AccountReference;
import com.android.bedstead.nene.devicepolicy.CommonDevicePolicy;
import com.android.bedstead.nene.devicepolicy.DeviceOwner;
import com.android.bedstead.nene.devicepolicy.DeviceOwnerType;
import com.android.bedstead.nene.devicepolicy.DevicePolicyController;
import com.android.bedstead.nene.devicepolicy.ProfileOwner;
import com.android.bedstead.nene.exceptions.AdbException;
import com.android.bedstead.nene.exceptions.NeneException;
import com.android.bedstead.nene.flags.Flags;
import com.android.bedstead.nene.logcat.SystemServerException;
import com.android.bedstead.nene.packages.ComponentReference;
import com.android.bedstead.nene.packages.Package;
import com.android.bedstead.nene.permissions.PermissionContext;
import com.android.bedstead.nene.permissions.PermissionContextImpl;
import com.android.bedstead.nene.types.OptionalBoolean;
import com.android.bedstead.nene.users.UserBuilder;
import com.android.bedstead.nene.users.UserReference;
import com.android.bedstead.nene.utils.Poll;
import com.android.bedstead.nene.utils.ShellCommand;
import com.android.bedstead.nene.utils.Tags;
import com.android.bedstead.nene.utils.Versions;
import com.android.bedstead.remoteaccountauthenticator.RemoteAccountAuthenticator;
import com.android.bedstead.remotedpc.RemoteDelegate;
import com.android.bedstead.remotedpc.RemoteDevicePolicyManagerRoleHolder;
import com.android.bedstead.remotedpc.RemoteDpc;
import com.android.bedstead.remotedpc.RemoteDpcUsingParentInstance;
import com.android.bedstead.remotedpc.RemotePolicyManager;
import com.android.bedstead.remotedpc.RemoteTestApp;
import com.android.bedstead.testapp.TestApp;
import com.android.bedstead.testapp.TestAppInstance;
import com.android.bedstead.testapp.TestAppProvider;
import com.android.bedstead.testapp.TestAppQueryBuilder;
import com.android.compatibility.common.util.BlockingBroadcastReceiver;
import com.android.eventlib.EventLogs;
import com.android.queryable.annotations.Query;
import com.google.common.base.Objects;
import junit.framework.AssertionFailedError;
import org.junit.Assume;
import org.junit.AssumptionViolatedException;
import org.junit.runner.Description;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* A Junit rule which exposes methods for efficiently changing and querying device state.
*
* <p>States set by the methods on this class will by default be cleaned up after the test.
*
*
* <p>Using this rule also enforces preconditions in annotations from the
* {@code com.android.comaptibility.common.util.enterprise.annotations} package.
* <p>
* {@code assumeTrue} will be used, so tests which do not meet preconditions will be skipped.
*/
public final class DeviceState extends HarrierRule {
private static final String SWITCHED_TO_USER = "switchedToUser";
private static final String SWITCHED_TO_PARENT_USER = "switchedToParentUser";
public static final String INSTALL_INSTRUMENTED_APP = "installInstrumentedApp";
private static final String IS_QUIET_MODE_ENABLED = "isQuietModeEnabled";
public static final String FOR_USER = "forUser";
public static final String DPC_IS_PRIMARY = "dpcIsPrimary";
public static final String AFFILIATION_IDS = "affiliationIds";
private static final String USE_PARENT_INSTANCE_OF_DPC = "useParentInstanceOfDpc";
private final Context mContext = TestApis.context().instrumentedContext();
private static final String SKIP_TEST_TEARDOWN_KEY = "skip-test-teardown";
private static final String SKIP_CLASS_TEARDOWN_KEY = "skip-class-teardown";
private static final String SKIP_TESTS_REASON_KEY = "skip-tests-reason";
private static final String MIN_SDK_VERSION_KEY = "min-sdk-version";
private static final String PERMISSIONS_INSTRUMENTATION_PACKAGE_KEY =
"permission-instrumentation-package";
private static final String ADDITIONAL_FEATURES_KEY = "additional-features";
private boolean mSkipTestTeardown;
private final boolean mSkipClassTeardown;
private boolean mSkipTests;
private boolean mFailTests;
private boolean mUsingBedsteadJUnit4 = false;
private String mSkipTestsReason;
private String mFailTestsReason;
private final List<String> mAdditionalFeatures;
// The minimum version supported by tests, defaults to current version
private final int mMinSdkVersion;
private int mMinSdkVersionCurrentTest;
private final @Nullable String mPermissionsInstrumentationPackage;
private final Set<String> mPermissionsInstrumentationPackagePermissions = new HashSet<>();
// Marks if the conditions for requiring running under permission instrumentation have been set
// if not - we assume the test should never run under permission instrumentation
// This is only used if a permission instrumentation package is set
private boolean mHasRequirePermissionInstrumentation = false;
private boolean mNextSafetyOperationSet = false;
private static final String TV_PROFILE_TYPE_NAME = "com.android.tv.profile";
private static final String CLONE_PROFILE_TYPE_NAME = "android.os.usertype.profile.CLONE";
private static final String PRIVATE_PROFILE_TYPE_NAME = "android.os.usertype.profile.PRIVATE";
// We timeout 10 seconds before the infra would timeout
private static final Duration MAX_TEST_DURATION =
Duration.ofMillis(
Long.parseLong(TestApis.instrumentation().arguments().getString(
"timeout_msec", "600000")) - 2000);
// We allow overriding the limit on a class-by-class basis
private final Duration mMaxTestDuration;
private ExecutorService mTestExecutor;
private Thread mTestThread;
private final PackageManager mPackageManager = sContext.getPackageManager();
public static final class Builder {
private Duration mMaxTestDuration = MAX_TEST_DURATION;
private Builder() {
}
public Builder maxTestDuration(Duration maxTestDuration) {
mMaxTestDuration = maxTestDuration;
return this;
}
public DeviceState build() {
return new DeviceState(mMaxTestDuration);
}
}
public static Builder builder() {
return new Builder();
}
public DeviceState(Duration maxTestDuration) {
mMaxTestDuration = maxTestDuration;
mSkipTestTeardown = TestApis.instrumentation().arguments().getBoolean(
SKIP_TEST_TEARDOWN_KEY, false);
mSkipClassTeardown = TestApis.instrumentation().arguments().getBoolean(
SKIP_CLASS_TEARDOWN_KEY, false);
mSkipTestsReason = TestApis.instrumentation().arguments().getString(SKIP_TESTS_REASON_KEY,
"");
mSkipTests = !mSkipTestsReason.isEmpty();
mMinSdkVersion = TestApis.instrumentation().arguments().getInt(MIN_SDK_VERSION_KEY,
SDK_INT);
mPermissionsInstrumentationPackage = TestApis.instrumentation().arguments().getString(
PERMISSIONS_INSTRUMENTATION_PACKAGE_KEY);
mAdditionalFeatures = Arrays.asList(TestApis.instrumentation().arguments().getString(
ADDITIONAL_FEATURES_KEY, "").split(","));
if (mPermissionsInstrumentationPackage != null) {
mPermissionsInstrumentationPackagePermissions.addAll(
TestApis.packages().find(mPermissionsInstrumentationPackage)
.requestedPermissions());
}
}
public DeviceState() {
this(MAX_TEST_DURATION);
}
@Override
void setSkipTestTeardown(boolean skipTestTeardown) {
mSkipTestTeardown = skipTestTeardown;
}
@Override
void setUsingBedsteadJUnit4(boolean usingBedsteadJUnit4) {
mUsingBedsteadJUnit4 = usingBedsteadJUnit4;
}
@Override
public Statement apply(Statement base, Description description) {
if (description.isTest()) {
return applyTest(base, description);
} else if (description.isSuite()) {
return applySuite(base, description);
}
throw new IllegalStateException("Unknown description type: " + description);
}
private Statement applyTest(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
Future<Throwable> future = mTestExecutor.submit(() -> {
try {
executeTest(base, description);
return null;
} catch (Throwable e) {
return e;
}
});
Throwable t;
try {
t = future.get(mMaxTestDuration.getSeconds(), TimeUnit.SECONDS);
} catch (TimeoutException e) {
StackTraceElement[] stack = mTestThread.getStackTrace();
future.cancel(true);
AssertionError assertionError = new AssertionError(
"Timed out executing test " + description.getDisplayName()
+ " after " + MAX_TEST_DURATION);
assertionError.setStackTrace(stack);
throw assertionError;
}
if (t != null) {
if (t.getStackTrace().length > 0) {
if (t.getStackTrace()[0].getMethodName().equals("createExceptionOrNull")) {
SystemServerException s = TestApis.logcat().findSystemServerException(
t);
if (s != null) {
throw s;
}
}
}
throw t;
}
}
};
}
private void executeTest(Statement base, Description description) throws Throwable {
String testName = description.getMethodName();
try {
prepareTestState(description);
base.evaluate();
} finally {
Log.d(LOG_TAG, "Tearing down state for test " + testName);
teardownNonShareableState();
if (!mSkipTestTeardown) {
teardownShareableState();
}
Log.d(LOG_TAG, "Finished tearing down state for test " + testName);
}
}
void prepareTestState(Description description) throws Throwable {
String testName = description.getMethodName();
Log.d(LOG_TAG, "Preparing state for test " + testName);
if (mOriginalSwitchedUser == null) {
mOriginalSwitchedUser = TestApis.users().current();
}
testApps().snapshot();
Tags.clearTags();
Tags.addTag(Tags.USES_DEVICESTATE);
assumeFalse(mSkipTestsReason, mSkipTests);
assertFalse(mFailTestsReason, mFailTests);
TestApis.packages().features().addAll(mAdditionalFeatures);
// Ensure that tests only see events from the current test
EventLogs.resetLogs();
// Avoid cached activities on screen
TestApis.activities().clearAllActivities();
mMinSdkVersionCurrentTest = mMinSdkVersion;
List<Annotation> annotations = getAnnotations(description);
applyAnnotations(annotations, /* isTest= */ true);
String coexistenceOption = TestApis.instrumentation().arguments().getString("COEXISTENCE", "?");
if (coexistenceOption.equals("true")) {
ensureFeatureFlagEnabled(NAMESPACE_DEVICE_POLICY_MANAGER, PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG);
ensureFeatureFlagEnabled(NAMESPACE_DEVICE_POLICY_MANAGER, ENABLE_DEVICE_POLICY_ENGINE_FLAG);
} else if (coexistenceOption.equals("false")) {
ensureFeatureFlagNotEnabled(NAMESPACE_DEVICE_POLICY_MANAGER, PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG);
ensureFeatureFlagNotEnabled(NAMESPACE_DEVICE_POLICY_MANAGER, ENABLE_DEVICE_POLICY_ENGINE_FLAG);
}
Log.d(LOG_TAG, "Finished preparing state for test " + testName);
}
private void applyAnnotations(List<Annotation> annotations, boolean isTest)
throws Throwable {
Log.d(LOG_TAG, "Applying annotations: " + annotations);
for (Annotation annotation : annotations) {
Log.v(LOG_TAG, "Applying annotation " + annotation);
Class<? extends Annotation> annotationType = annotation.annotationType();
EnsureHasNoProfileAnnotation ensureHasNoProfileAnnotation =
annotationType.getAnnotation(EnsureHasNoProfileAnnotation.class);
if (ensureHasNoProfileAnnotation != null) {
UserType userType = (UserType) annotation.annotationType()
.getMethod(FOR_USER).invoke(annotation);
ensureHasNoProfile(ensureHasNoProfileAnnotation.value(), userType);
continue;
}
EnsureHasProfileAnnotation ensureHasProfileAnnotation =
annotationType.getAnnotation(EnsureHasProfileAnnotation.class);
if (ensureHasProfileAnnotation != null) {
UserType forUser = (UserType) annotation.annotationType()
.getMethod(FOR_USER).invoke(annotation);
OptionalBoolean installInstrumentedApp = (OptionalBoolean)
annotation.annotationType()
.getMethod(INSTALL_INSTRUMENTED_APP).invoke(annotation);
OptionalBoolean isQuietModeEnabled = OptionalBoolean.ANY;
try {
isQuietModeEnabled = (OptionalBoolean)
annotation.annotationType().getMethod(
IS_QUIET_MODE_ENABLED).invoke(annotation);
} catch (NoSuchMethodException e) {
// Expected, we default to ANY
}
boolean dpcIsPrimary = false;
boolean useParentInstance = false;
TestAppQueryBuilder dpcQuery = null;
if (ensureHasProfileAnnotation.hasProfileOwner()) {
// TODO(b/206441366): Add instant app support
requireNotInstantApp(
"Instant Apps cannot run Enterprise Tests", FailureMode.SKIP);
dpcIsPrimary = (boolean)
annotation.annotationType()
.getMethod(DPC_IS_PRIMARY).invoke(annotation);
if (dpcIsPrimary) {
useParentInstance = (boolean)
annotation.annotationType()
.getMethod(USE_PARENT_INSTANCE_OF_DPC).invoke(
annotation);
}
dpcQuery = getDpcQueryFromAnnotation(annotation);
}
OptionalBoolean switchedToParentUser = (OptionalBoolean)
annotation.annotationType()
.getMethod(SWITCHED_TO_PARENT_USER).invoke(annotation);
ensureHasProfile(
ensureHasProfileAnnotation.value(), installInstrumentedApp,
forUser, ensureHasProfileAnnotation.hasProfileOwner(),
dpcIsPrimary, useParentInstance, switchedToParentUser, isQuietModeEnabled,
getDpcKeyFromAnnotation(annotation, "dpcKey"), dpcQuery);
if (ensureHasProfileAnnotation.hasProfileOwner()) {
if (isOrganizationOwned(annotation)) {
ensureHasNoDeviceOwner(); // It doesn't make sense to have COPE + DO
}
((ProfileOwner) profileOwner(
workProfile(forUser)).devicePolicyController()).setIsOrganizationOwned(
isOrganizationOwned(annotation));
}
continue;
}
EnsureHasNoUserAnnotation ensureHasNoUserAnnotation =
annotationType.getAnnotation(EnsureHasNoUserAnnotation.class);
if (ensureHasNoUserAnnotation != null) {
ensureHasNoUser(ensureHasNoUserAnnotation.value());
continue;
}
EnsureHasUserAnnotation ensureHasUserAnnotation =
annotationType.getAnnotation(EnsureHasUserAnnotation.class);
if (ensureHasUserAnnotation != null) {
OptionalBoolean installInstrumentedApp = (OptionalBoolean)
annotation.annotationType()
.getMethod(INSTALL_INSTRUMENTED_APP).invoke(annotation);
OptionalBoolean switchedToUser = (OptionalBoolean)
annotation.annotationType()
.getMethod(SWITCHED_TO_USER).invoke(annotation);
ensureHasUser(
ensureHasUserAnnotation.value(), installInstrumentedApp,
switchedToUser);
continue;
}
if (annotation instanceof EnsureDefaultContentSuggestionsServiceEnabled ensureDefaultContentSuggestionsServiceEnabledAnnotation) {
ensureDefaultContentSuggestionsServiceEnabled(
ensureDefaultContentSuggestionsServiceEnabledAnnotation.onUser(),
/* enabled= */ true
);
continue;
}
if (annotation instanceof EnsureDefaultContentSuggestionsServiceDisabled ensureDefaultContentSuggestionsServiceDisabledAnnotation) {
ensureDefaultContentSuggestionsServiceEnabled(
ensureDefaultContentSuggestionsServiceDisabledAnnotation.onUser(),
/* enabled= */ false
);
continue;
}
if (annotation instanceof EnsureHasTestContentSuggestionsService ensureHasTestContentSuggestionsServiceAnnotation) {
ensureHasTestContentSuggestionsService(
ensureHasTestContentSuggestionsServiceAnnotation.onUser());
continue;
}
if (annotation instanceof EnsureHasAdditionalUser ensureHasAdditionalUserAnnotation) {
ensureHasAdditionalUser(
ensureHasAdditionalUserAnnotation.installInstrumentedApp(),
ensureHasAdditionalUserAnnotation.switchedToUser());
continue;
}
if (annotation instanceof EnsureHasNoAdditionalUser) {
ensureHasNoAdditionalUser();
continue;
}
if (annotation instanceof RequireRunOnAdditionalUser requireRunOnAdditionalUserAnnotation) {
requireRunOnAdditionalUser(
requireRunOnAdditionalUserAnnotation.switchedToUser());
continue;
}
RequireRunOnUserAnnotation requireRunOnUserAnnotation =
annotationType.getAnnotation(RequireRunOnUserAnnotation.class);
if (requireRunOnUserAnnotation != null) {
OptionalBoolean switchedToUser = (OptionalBoolean)
annotation.annotationType()
.getMethod(SWITCHED_TO_USER).invoke(annotation);
requireRunOnUser(requireRunOnUserAnnotation.value(), switchedToUser);
continue;
}
if (annotation instanceof TestTag testTagAnnotation) {
Tags.addTag(testTagAnnotation.value());
}
RequireRunOnProfileAnnotation requireRunOnProfileAnnotation =
annotationType.getAnnotation(RequireRunOnProfileAnnotation.class);
if (requireRunOnProfileAnnotation != null) {
OptionalBoolean installInstrumentedAppInParent = (OptionalBoolean)
annotation.annotationType()
.getMethod("installInstrumentedAppInParent")
.invoke(annotation);
OptionalBoolean switchedToParentUser = (OptionalBoolean)
annotation.annotationType()
.getMethod(SWITCHED_TO_PARENT_USER).invoke(annotation);
boolean dpcIsPrimary = false;
Set<String> affiliationIds = null;
TestAppQueryBuilder dpcQuery = null;
if (requireRunOnProfileAnnotation.hasProfileOwner()) {
dpcIsPrimary = (boolean)
annotation.annotationType()
.getMethod(DPC_IS_PRIMARY).invoke(annotation);
affiliationIds = new HashSet<>(Arrays.asList((String[])
annotation.annotationType()
.getMethod(AFFILIATION_IDS).invoke(annotation)));
dpcQuery = getDpcQueryFromAnnotation(annotation);
}
requireRunOnProfile(requireRunOnProfileAnnotation.value(),
installInstrumentedAppInParent,
requireRunOnProfileAnnotation.hasProfileOwner(),
dpcIsPrimary, /* useParentInstance= */ false,
switchedToParentUser, affiliationIds,
getDpcKeyFromAnnotation(annotation, "dpcKey"), dpcQuery);
if (requireRunOnProfileAnnotation.hasProfileOwner()) {
if (isOrganizationOwned(annotation)) {
ensureHasNoDeviceOwner(); // It doesn't make sense to have COPE + DO
}
((ProfileOwner) profileOwner(
workProfile()).devicePolicyController()).setIsOrganizationOwned(
isOrganizationOwned(annotation));
}
continue;
}
if (annotation instanceof AdditionalQueryParameters additionalQueryParametersAnnotation) {
mAdditionalQueryParameters.put(
additionalQueryParametersAnnotation.forTestApp(),
additionalQueryParametersAnnotation.query());
}
if (annotation instanceof EnsureTestAppInstalled ensureTestAppInstalledAnnotation) {
ensureTestAppInstalled(
ensureTestAppInstalledAnnotation.key(),
ensureTestAppInstalledAnnotation.query(),
ensureTestAppInstalledAnnotation.onUser(),
ensureTestAppInstalledAnnotation.isPrimary()
);
continue;
}
if (annotation instanceof EnsureTestAppHasPermission ensureTestAppHasPermissionAnnotation) {
ensureTestAppHasPermission(
ensureTestAppHasPermissionAnnotation.testAppKey(),
ensureTestAppHasPermissionAnnotation.value(),
ensureTestAppHasPermissionAnnotation.minVersion(),
ensureTestAppHasPermissionAnnotation.maxVersion(),
ensureTestAppHasPermissionAnnotation.failureMode()
);
continue;
}
if (annotation instanceof EnsureTestAppDoesNotHavePermission ensureTestAppDoesNotHavePermissionAnnotation) {
ensureTestAppDoesNotHavePermission(
ensureTestAppDoesNotHavePermissionAnnotation.testAppKey(),
ensureTestAppDoesNotHavePermissionAnnotation.value(),
ensureTestAppDoesNotHavePermissionAnnotation.failureMode()
);
continue;
}
if (annotation instanceof EnsureTestAppHasAppOp ensureTestAppHasAppOpAnnotation) {
ensureTestAppHasAppOp(
ensureTestAppHasAppOpAnnotation.testAppKey(),
ensureTestAppHasAppOpAnnotation.value(),
ensureTestAppHasAppOpAnnotation.minVersion(),
ensureTestAppHasAppOpAnnotation.maxVersion()
);
continue;
}
if (annotation instanceof EnsureHasDelegate ensureHasDelegateAnnotation) {
ensureHasDelegate(
ensureHasDelegateAnnotation.admin(),
Arrays.asList(ensureHasDelegateAnnotation.scopes()),
ensureHasDelegateAnnotation.isPrimary());
continue;
}
if (annotation instanceof EnsureHasDevicePolicyManagerRoleHolder ensureHasDevicePolicyManagerRoleHolder) {
ensureHasDevicePolicyManagerRoleHolder(
ensureHasDevicePolicyManagerRoleHolder.onUser(),
ensureHasDevicePolicyManagerRoleHolder.isPrimary());
}
if (annotation instanceof EnsureHasDeviceOwner ensureHasDeviceOwnerAnnotation) {
ensureHasDeviceOwner(ensureHasDeviceOwnerAnnotation.failureMode(),
ensureHasDeviceOwnerAnnotation.isPrimary(),
ensureHasDeviceOwnerAnnotation.headlessDeviceOwnerType(),
new HashSet<>(
Arrays.asList(
ensureHasDeviceOwnerAnnotation.affiliationIds())),
ensureHasDeviceOwnerAnnotation.type(),
getDpcKeyFromAnnotation(annotation, "key"),
getDpcQueryFromAnnotation(annotation));
continue;
}
if (annotation instanceof EnsureHasNoDelegate ensureHasNoDelegateAnnotation) {
ensureHasNoDelegate(ensureHasNoDelegateAnnotation.admin());
continue;
}
if (annotation instanceof EnsureHasNoDeviceOwner) {
ensureHasNoDeviceOwner();
continue;
}
if (annotation instanceof RequireFeature requireFeatureAnnotation) {
requireFeature(
requireFeatureAnnotation.value(),
requireFeatureAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireDoesNotHaveFeature requireDoesNotHaveFeatureAnnotation) {
requireDoesNotHaveFeature(
requireDoesNotHaveFeatureAnnotation.value(),
requireDoesNotHaveFeatureAnnotation.failureMode());
continue;
}
if (annotation instanceof EnsureHasProfileOwner ensureHasProfileOwnerAnnotation) {
ensureHasProfileOwner(ensureHasProfileOwnerAnnotation.onUser(),
ensureHasProfileOwnerAnnotation.isPrimary(),
ensureHasProfileOwnerAnnotation.useParentInstance(),
new HashSet<>(Arrays.asList(
ensureHasProfileOwnerAnnotation.affiliationIds())),
ensureHasProfileOwnerAnnotation.key(),
getDpcQueryFromAnnotation(annotation));
continue;
}
if (annotationType.equals(EnsureHasNoProfileOwner.class)) {
EnsureHasNoProfileOwner ensureHasNoProfileOwnerAnnotation =
(EnsureHasNoProfileOwner) annotation;
ensureHasNoProfileOwner(ensureHasNoProfileOwnerAnnotation.onUser());
continue;
}
if (annotation instanceof RequireUserSupported requireUserSupportedAnnotation) {
requireUserSupported(
requireUserSupportedAnnotation.value(),
requireUserSupportedAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireLowRamDevice requireLowRamDeviceAnnotation) {
requireLowRamDevice(requireLowRamDeviceAnnotation.reason(),
requireLowRamDeviceAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireNotLowRamDevice requireNotLowRamDeviceAnnotation) {
requireNotLowRamDevice(requireNotLowRamDeviceAnnotation.reason(),
requireNotLowRamDeviceAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireVisibleBackgroundUsers) {
RequireVisibleBackgroundUsers castedAnnotation =
(RequireVisibleBackgroundUsers) annotation;
requireVisibleBackgroundUsersSupported(castedAnnotation.reason(),
castedAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireNotVisibleBackgroundUsers) {
RequireNotVisibleBackgroundUsers castedAnnotation =
(RequireNotVisibleBackgroundUsers) annotation;
requireVisibleBackgroundUsersNotSupported(castedAnnotation.reason(),
castedAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireVisibleBackgroundUsersOnDefaultDisplay) {
RequireVisibleBackgroundUsersOnDefaultDisplay castedAnnotation =
(RequireVisibleBackgroundUsersOnDefaultDisplay) annotation;
requireVisibleBackgroundUsersOnDefaultDisplaySupported(castedAnnotation.reason(),
castedAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireNotVisibleBackgroundUsersOnDefaultDisplay) {
RequireNotVisibleBackgroundUsersOnDefaultDisplay castedAnnotation =
(RequireNotVisibleBackgroundUsersOnDefaultDisplay) annotation;
requireVisibleBackgroundUsersOnDefaultDisplayNotSupported(castedAnnotation.reason(),
castedAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireRunOnVisibleBackgroundNonProfileUser) {
if (!isNonProfileUserRunningVisibleOnBackground()) {
failOrSkip("Test only runs non-profile user that's running visible in the "
+ "background", FailureMode.SKIP);
}
continue;
}
if (annotation instanceof RequireRunNotOnVisibleBackgroundNonProfileUser) {
if (isNonProfileUserRunningVisibleOnBackground()) {
failOrSkip("Test cannot run on non-profile user that's running visible in the "
+ "background", FailureMode.SKIP);
}
continue;
}
if (annotation instanceof RequireSystemServiceAvailable requireSystemServiceAvailableAnnotation) {
requireSystemServiceAvailable(requireSystemServiceAvailableAnnotation.value(),
requireSystemServiceAvailableAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireTargetSdkVersion requireTargetSdkVersionAnnotation) {
requireTargetSdkVersion(
requireTargetSdkVersionAnnotation.min(),
requireTargetSdkVersionAnnotation.max(),
requireTargetSdkVersionAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireSdkVersion requireSdkVersionAnnotation) {
if (requireSdkVersionAnnotation.reason().isEmpty()) {
requireSdkVersion(
requireSdkVersionAnnotation.min(),
requireSdkVersionAnnotation.max(),
requireSdkVersionAnnotation.failureMode());
} else {
requireSdkVersion(
requireSdkVersionAnnotation.min(),
requireSdkVersionAnnotation.max(),
requireSdkVersionAnnotation.failureMode(),
requireSdkVersionAnnotation.reason());
}
continue;
}
if (annotation instanceof RequirePackageInstalled requirePackageInstalledAnnotation) {
requirePackageInstalled(
requirePackageInstalledAnnotation.value(),
requirePackageInstalledAnnotation.onUser(),
requirePackageInstalledAnnotation.failureMode());
continue;
}
if (annotation instanceof RequirePackageNotInstalled requirePackageNotInstalledAnnotation) {
requirePackageNotInstalled(
requirePackageNotInstalledAnnotation.value(),
requirePackageNotInstalledAnnotation.onUser(),
requirePackageNotInstalledAnnotation.failureMode()
);
continue;
}
if (annotation instanceof EnsurePackageNotInstalled ensurePackageNotInstalledAnnotation) {
ensurePackageNotInstalled(
ensurePackageNotInstalledAnnotation.value(),
ensurePackageNotInstalledAnnotation.onUser()
);
continue;
}
if (annotation instanceof RequireNotHeadlessSystemUserMode requireNotHeadlessSystemUserModeAnnotation) {
requireNotHeadlessSystemUserMode(
requireNotHeadlessSystemUserModeAnnotation.reason());
continue;
}
if (annotation instanceof RequireHeadlessSystemUserMode requireHeadlessSystemUserModeAnnotation) {
requireHeadlessSystemUserMode(requireHeadlessSystemUserModeAnnotation.reason());
continue;
}
if (annotation instanceof EnsureCanGetPermission ensureCanGetPermissionAnnotation) {
if (!meetsSdkVersionRequirements(
ensureCanGetPermissionAnnotation.minVersion(),
ensureCanGetPermissionAnnotation.maxVersion())) {
Log.d(LOG_TAG,
"Version " + SDK_INT + " does not need to get permissions "
+ Arrays.toString(
ensureCanGetPermissionAnnotation.value()));
continue;
}
for (String permission : ensureCanGetPermissionAnnotation.value()) {
ensureCanGetPermission(permission);
}
continue;
}
if (annotation instanceof EnsureHasAppOp ensureHasAppOpAnnotation) {
if (!meetsSdkVersionRequirements(
ensureHasAppOpAnnotation.minVersion(),
ensureHasAppOpAnnotation.maxVersion())) {
Log.d(LOG_TAG,
"Version " + SDK_INT + " does not need to get appOp "
+ ensureHasAppOpAnnotation.value());
continue;
}
try {
withAppOp(ensureHasAppOpAnnotation.value());
} catch (NeneException e) {
failOrSkip("Error getting appOp: " + e,
ensureHasAppOpAnnotation.failureMode());
}
continue;
}
if (annotation instanceof EnsureDoesNotHaveAppOp ensureDoesNotHaveAppOpAnnotation) {
try {
withoutAppOp(ensureDoesNotHaveAppOpAnnotation.value());
} catch (NeneException e) {
failOrSkip("Error denying appOp: " + e,
ensureDoesNotHaveAppOpAnnotation.failureMode());
}
continue;
}
if (annotation instanceof EnsureHasPermission ensureHasPermissionAnnotation) {
if (!meetsSdkVersionRequirements(
ensureHasPermissionAnnotation.minVersion(),
ensureHasPermissionAnnotation.maxVersion())) {
Log.d(LOG_TAG,
"Version " + SDK_INT + " does not need to get permission "
+ Arrays.toString(ensureHasPermissionAnnotation.value()));
continue;
}
try {
for (String permission : ensureHasPermissionAnnotation.value()) {
ensureCanGetPermission(permission);
}
withPermission(ensureHasPermissionAnnotation.value());
} catch (NeneException e) {
failOrSkip("Error getting permission: " + e,
ensureHasPermissionAnnotation.failureMode());
}
continue;
}
if (annotation instanceof EnsureDoesNotHavePermission ensureDoesNotHavePermission) {
try {
withoutPermission(ensureDoesNotHavePermission.value());
} catch (NeneException e) {
failOrSkip("Error denying permission: " + e,
ensureDoesNotHavePermission.failureMode());
}
continue;
}
if (annotation instanceof EnsureScreenIsOn) {
ensureScreenIsOn();
continue;
}
if (annotation instanceof EnsureUnlocked) {
ensureUnlocked();
continue;
}
if (annotation instanceof EnsurePasswordSet ensurePasswordSetAnnotation) {
ensurePasswordSet(
ensurePasswordSetAnnotation.forUser(),
ensurePasswordSetAnnotation.password());
continue;
}
if (annotation instanceof EnsurePasswordNotSet ensurePasswordNotSetAnnotation) {
ensurePasswordNotSet(ensurePasswordNotSetAnnotation.forUser());
continue;
}
if (annotation instanceof OtherUser otherUserAnnotation) {
mOtherUserType = otherUserAnnotation.value();
continue;
}
if (annotation instanceof EnsureBluetoothEnabled) {
ensureBluetoothEnabled();
continue;
}
if (annotation instanceof EnsureBluetoothDisabled) {
ensureBluetoothDisabled();
continue;
}
if (annotation instanceof EnsureWifiEnabled) {
ensureWifiEnabled();
continue;
}
if (annotation instanceof EnsureWifiDisabled) {
ensureWifiDisabled();
continue;
}
if (annotation instanceof EnsureSecureSettingSet ensureSecureSettingSetAnnotation) {
ensureSecureSettingSet(
ensureSecureSettingSetAnnotation.onUser(),
ensureSecureSettingSetAnnotation.key(),
ensureSecureSettingSetAnnotation.value());
continue;
}
if (annotation instanceof EnsurePropertySet ensurePropertySetAnnotation) {
ensurePropertySet(
ensurePropertySetAnnotation.key(), ensurePropertySetAnnotation.value());
continue;
}
if (annotation instanceof EnsureGlobalSettingSet ensureGlobalSettingSetAnnotation) {
ensureGlobalSettingSet(
ensureGlobalSettingSetAnnotation.key(),
ensureGlobalSettingSetAnnotation.value());
continue;
}
if (annotation instanceof RequireMultiUserSupport requireMultiUserSupportAnnotation) {
requireMultiUserSupport(requireMultiUserSupportAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireHasPolicyExemptApps requireHasPolicyExemptAppsAnnotation) {
requireHasPolicyExemptApps(requireHasPolicyExemptAppsAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireInstantApp requireInstantAppAnnotation) {
requireInstantApp(requireInstantAppAnnotation.reason(),
requireInstantAppAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireNotInstantApp requireNotInstantAppAnnotation) {
requireNotInstantApp(requireNotInstantAppAnnotation.reason(),
requireNotInstantAppAnnotation.failureMode());
continue;
}
if (annotation instanceof EnsureCanAddUser ensureCanAddUserAnnotation) {
ensureCanAddUser(
ensureCanAddUserAnnotation.number(),
ensureCanAddUserAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireFeatureFlagEnabled requireFeatureFlagEnabledAnnotation) {
requireFeatureFlagEnabled(
requireFeatureFlagEnabledAnnotation.namespace(),
requireFeatureFlagEnabledAnnotation.key(),
requireFeatureFlagEnabledAnnotation.failureMode());
continue;
}
if (annotation instanceof EnsureFeatureFlagEnabled ensureFeatureFlagEnabledAnnotation) {
ensureFeatureFlagEnabled(
ensureFeatureFlagEnabledAnnotation.namespace(),
ensureFeatureFlagEnabledAnnotation.key());
continue;
}
if (annotation instanceof RequireFeatureFlagNotEnabled requireFeatureFlagNotEnabledAnnotation) {
requireFeatureFlagNotEnabled(
requireFeatureFlagNotEnabledAnnotation.namespace(),
requireFeatureFlagNotEnabledAnnotation.key(),
requireFeatureFlagNotEnabledAnnotation.failureMode());
continue;
}
if (annotation instanceof EnsureFeatureFlagNotEnabled ensureFeatureFlagNotEnabledAnnotation) {
ensureFeatureFlagNotEnabled(
ensureFeatureFlagNotEnabledAnnotation.namespace(),
ensureFeatureFlagNotEnabledAnnotation.key());
continue;
}
if (annotation instanceof RequireFeatureFlagValue requireFeatureFlagValueAnnotation) {
requireFeatureFlagValue(
requireFeatureFlagValueAnnotation.namespace(),
requireFeatureFlagValueAnnotation.key(),
requireFeatureFlagValueAnnotation.value(),
requireFeatureFlagValueAnnotation.failureMode());
continue;
}
if (annotation instanceof EnsureFeatureFlagValue ensureFeatureFlagValueAnnotation) {
ensureFeatureFlagValue(
ensureFeatureFlagValueAnnotation.namespace(),
ensureFeatureFlagValueAnnotation.key(),
ensureFeatureFlagValueAnnotation.value());
continue;
}
UsesAnnotationExecutor usesAnnotationExecutorAnnotation =
annotationType.getAnnotation(UsesAnnotationExecutor.class);
if (usesAnnotationExecutorAnnotation != null) {
AnnotationExecutor executor =
getAnnotationExecutor(usesAnnotationExecutorAnnotation.value());
executor.applyAnnotation(annotation);
continue;
}
if (annotation instanceof EnsureHasAccountAuthenticator ensureHasAccountAuthenticatorAnnotation) {
ensureHasAccountAuthenticator(ensureHasAccountAuthenticatorAnnotation.onUser());
continue;
}
if (annotation instanceof EnsureHasAccount ensureHasAccountAnnotation) {
ensureHasAccount(
ensureHasAccountAnnotation.onUser(),
ensureHasAccountAnnotation.key(),
ensureHasAccountAnnotation.features());
continue;
}
if (annotation instanceof EnsureHasAccounts ensureHasAccountsAnnotation) {
ensureHasAccounts(ensureHasAccountsAnnotation.value());
continue;
}
if (annotation instanceof EnsureHasNoAccounts ensureHasNoAccountsAnnotation) {
ensureHasNoAccounts(ensureHasNoAccountsAnnotation.onUser(),
ensureHasNoAccountsAnnotation.allowPreCreatedAccounts(),
ensureHasNoAccountsAnnotation.failureMode());
continue;
}
if (annotation instanceof EnsureHasUserRestriction ensureHasUserRestrictionAnnotation) {
ensureHasUserRestriction(
ensureHasUserRestrictionAnnotation.value(),
ensureHasUserRestrictionAnnotation.onUser());
continue;
}
if (annotation instanceof EnsureDoesNotHaveUserRestriction ensureDoesNotHaveUserRestrictionAnnotation) {
ensureDoesNotHaveUserRestriction(
ensureDoesNotHaveUserRestrictionAnnotation.value(),
ensureDoesNotHaveUserRestrictionAnnotation.onUser());
continue;
}
if (annotation instanceof RequireQuickSettingsSupport requireQuickSettingsSupport) {
checkFailOrSkip("Device does not have quick settings",
TileService.isQuickSettingsSupported(),
requireQuickSettingsSupport.failureMode());
continue;
}
if (annotation instanceof RequireHasDefaultBrowser requireHasDefaultBrowser) {
UserReference user =
resolveUserTypeToUser(requireHasDefaultBrowser.forUser());
checkFailOrSkip("User: " + user + " does not have a default browser",
!TestApis.roles().getRoleHoldersAsUser(ROLE_BROWSER, user).isEmpty(),
requireHasDefaultBrowser.failureMode());
continue;
}
if (annotation instanceof RequireHasDefaultBrowser) {
RequireHasDefaultBrowser requireHasDefaultBrowser =
(RequireHasDefaultBrowser) annotation;
UserReference user =
resolveUserTypeToUser(requireHasDefaultBrowser.forUser());
checkFailOrSkip("User: " + user + " does not have a default browser",
TestApis.packages().defaultBrowserForUser(user) != null,
requireHasDefaultBrowser.failureMode());
continue;
}
if (annotation instanceof RequireTelephonySupport requireTelephonySupport) {
checkFailOrSkip("Device does not have telephony support",
mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY),
requireTelephonySupport.failureMode());
continue;
}
if (annotation instanceof MostImportantCoexistenceTest mostImportantCoexistenceTestAnnotation) {
mTestApps.put(MostImportantCoexistenceTest.MORE_IMPORTANT, deviceOwner());
EnterprisePolicy[] policies =
mostImportantCoexistenceTestAnnotation.policy()
.getAnnotationsByType(EnterprisePolicy.class);
if (policies[0].permissions().length != 1) {
throw new IllegalStateException(
"Cannot use MostImportantCoexistenceTest for policies which have"
+ " 0 or 2+ permissions");
}
String[] permission = new String[]{policies[0].permissions()[0].appliedWith()};
ensureTestAppHasPermission(MostImportantCoexistenceTest.MORE_IMPORTANT, permission,
0, Integer.MAX_VALUE, FailureMode.SKIP);
ensureTestAppHasPermission(MostImportantCoexistenceTest.LESS_IMPORTANT, permission,
0, Integer.MAX_VALUE, FailureMode.SKIP);
continue;
}
if (annotation instanceof MostRestrictiveCoexistenceTest mostRestrictiveCoexistenceTestAnnotation) {
EnterprisePolicy[] policies =
mostRestrictiveCoexistenceTestAnnotation.policy()
.getAnnotationsByType(EnterprisePolicy.class);
if (policies[0].permissions().length != 1) {
throw new IllegalStateException(
"Cannot use MostRestrictiveCoexistenceTest for policies which have "
+ "0 or 2+ permissions");
}
String[] permission = new String[]{policies[0].permissions()[0].appliedWith()};
ensureTestAppHasPermission(MostRestrictiveCoexistenceTest.DPC_1, permission,
0, Integer.MAX_VALUE, FailureMode.SKIP);
ensureTestAppHasPermission(MostRestrictiveCoexistenceTest.DPC_2, permission,
0, Integer.MAX_VALUE, FailureMode.SKIP);
continue;
}
if (annotation instanceof RequireStorageEncryptionSupported) {
requireStorageEncryptionSupported();
continue;
}
if (annotation instanceof RequireStorageEncryptionUnsupported) {
requireStorageEncryptionUnsupported();
continue;
}
if (annotation instanceof RequireAdbRoot requireAdbRootAnnotation) {
requireAdbRoot(requireAdbRootAnnotation.failureMode());
continue;
}
if (annotation instanceof RequireAdbOverWifi requireAdbOverWifiAnnotation) {
requireAdbOverWifi(requireAdbOverWifiAnnotation.failureMode());
continue;
}
if (annotation instanceof EnsurePolicyOperationUnsafe ensurePolicyOperationUnsafeAnnotation) {
ensurePolicyOperationUnsafe(ensurePolicyOperationUnsafeAnnotation.operation(),
ensurePolicyOperationUnsafeAnnotation.reason());
continue;
}
if (annotation instanceof RequireFactoryResetProtectionPolicySupported) {
requireFactoryResetProtectionPolicySupported();
continue;
}
}
requireSdkVersion(/* min= */ mMinSdkVersionCurrentTest,
/* max= */ Integer.MAX_VALUE, FailureMode.SKIP);
if (isTest && mPermissionsInstrumentationPackage != null
&& !mHasRequirePermissionInstrumentation) {
requireNoPermissionsInstrumentation("No reason to use instrumentation");
}
}
private static TestAppQueryBuilder defaultDpcQuery() {
return new TestAppProvider().query()
.wherePackageName().isEqualTo(RemoteDpc.REMOTE_DPC_APP_PACKAGE_NAME_OR_PREFIX);
}
private static TestAppQueryBuilder getDpcQueryFromAnnotation(Annotation annotation) {
try {
Method queryMethod = annotation.annotationType().getMethod("dpc");
Query query = (Query) queryMethod.invoke(annotation);
TestAppQueryBuilder queryBuilder = new TestAppProvider().query(query);
return queryBuilder;
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
Log.i(LOG_TAG, "Unable to get dpc query value for "
+ annotation.annotationType().getName(), e);
}
return new TestAppProvider().query(); // No dpc specified - use any
}
private static String getDpcKeyFromAnnotation(Annotation annotation, String keyName) {
try {
Method keyMethod = annotation.annotationType().getMethod(keyName);
return (String) keyMethod.invoke(annotation);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
Log.i(LOG_TAG, "Unable to get dpc key (" + keyName + ") value for "
+ annotation.annotationType().getName(), e);
return null;
}
}
private List<Annotation> getAnnotations(Description description) {
if (mUsingBedsteadJUnit4 && description.isTest()) {
// The annotations are already exploded for tests
return new ArrayList<>(description.getAnnotations());
}
// Otherwise we should build a new collection by recursively gathering annotations
// if we find any which don't work without the runner we should error and fail the test
List<Annotation> annotations = new ArrayList<>();
if (description.isTest()) {
annotations =
new ArrayList<>(Arrays.asList(description.getTestClass().getAnnotations()));
}
annotations.addAll(description.getAnnotations());
annotations.sort(BedsteadJUnit4::annotationSorter);
checkAnnotations(annotations);
BedsteadJUnit4.resolveRecursiveAnnotations(this, annotations,
/* parameterizedAnnotation= */ null);
checkAnnotations(annotations);
return annotations;
}
private void checkAnnotations(Collection<Annotation> annotations) {
if (mUsingBedsteadJUnit4) {
return;
}
for (Annotation annotation : annotations) {
if (annotation.annotationType().getAnnotation(RequiresBedsteadJUnit4.class) != null
|| annotation.annotationType().getAnnotation(
ParameterizedAnnotation.class) != null) {
throw new AssertionFailedError("Test is annotated "
+ annotation.annotationType().getSimpleName()
+ " which requires using the BedsteadJUnit4 test runner");
}
}
}
private Statement applySuite(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
mTestExecutor = Executors.newSingleThreadExecutor();
Future<Thread> testThreadFuture = mTestExecutor.submit(Thread::currentThread);
try {
mTestThread = testThreadFuture.get();
} catch (InterruptedException | ExecutionException e) {
throw new AssertionError(
"Error setting up DeviceState. Interrupted getting test thread", e);
}
checkValidAnnotations(description);
TestClass testClass = new TestClass(description.getTestClass());
PermissionContextImpl permissionContext = null;
if (mSkipTests || mFailTests) {
Log.d(
LOG_TAG,
"Skipping suite setup and teardown due to skipTests: "
+ mSkipTests
+ ", failTests: "
+ mFailTests);
base.evaluate();
return;
}
Log.d(LOG_TAG, "Preparing state for suite " + description.getClassName());
Tags.clearTags();
Tags.addTag(Tags.USES_DEVICESTATE);
if (TestApis.packages().instrumented().isInstantApp()) {
Tags.addTag(Tags.INSTANT_APP);
}
boolean originalFlagSyncEnabled = TestApis.flags().getFlagSyncEnabled();
try {
TestApis.device().keepScreenOn(true);
if (Versions.meetsMinimumSdkVersionRequirement(T)) {
TestApis.flags().setFlagSyncEnabled(false);
}
if (!Tags.hasTag(Tags.INSTANT_APP)) {
TestApis.device().setKeyguardEnabled(false);
}
TestApis.users().setStopBgUsersOnSwitch(OptionalBoolean.FALSE);
try {
List<Annotation> annotations = new ArrayList<>(getAnnotations(description));
applyAnnotations(annotations, /* isTest= */ false);
} catch (AssumptionViolatedException e) {
Log.i(LOG_TAG, "Assumption failed during class setup", e);
mSkipTests = true;
mSkipTestsReason = e.getMessage();
} catch (AssertionError e) {
Log.i(LOG_TAG, "Assertion failed during class setup", e);
mFailTests = true;
mFailTestsReason = e.getMessage();
}
Log.d(
LOG_TAG,
"Finished preparing state for suite " + description.getClassName());
if (!mSkipTests && !mFailTests) {
// Tests may be skipped during the class setup
runAnnotatedMethods(testClass, BeforeClass.class);
}
base.evaluate();
} finally {
runAnnotatedMethods(testClass, AfterClass.class);
if (permissionContext != null) {
permissionContext.close();
}
if (!mSkipClassTeardown) {
teardownShareableState();
}
if (!Tags.hasTag(Tags.INSTANT_APP)) {
TestApis.device().setKeyguardEnabled(true);
}
// TODO(b/249710985): Reset to the default for the device or the previous value
// TestApis.device().keepScreenOn(false);
TestApis.users().setStopBgUsersOnSwitch(OptionalBoolean.ANY);
if (Versions.meetsMinimumSdkVersionRequirement(T)) {
TestApis.flags().setFlagSyncEnabled(originalFlagSyncEnabled);
}
Log.i(LOG_TAG, "Shutting down test thread executor");
mTestExecutor.shutdown();
}
}
};
}
private static final Map<Class<? extends Annotation>, Class<? extends Annotation>>
BANNED_ANNOTATIONS_TO_REPLACEMENTS = getBannedAnnotationsToReplacements();
private static Map<
Class<? extends Annotation>,
Class<? extends Annotation>> getBannedAnnotationsToReplacements() {
Map<
Class<? extends Annotation>,
Class<? extends Annotation>> bannedAnnotationsToReplacements = new HashMap<>();
bannedAnnotationsToReplacements.put(org.junit.BeforeClass.class, BeforeClass.class);
bannedAnnotationsToReplacements.put(org.junit.AfterClass.class, AfterClass.class);
return bannedAnnotationsToReplacements;
}
private void checkValidAnnotations(Description classDescription) {
for (Method method : classDescription.getTestClass().getMethods()) {
for (Map.Entry<
Class<? extends Annotation>,
Class<? extends Annotation>> bannedAnnotation
: BANNED_ANNOTATIONS_TO_REPLACEMENTS.entrySet()) {
if (method.isAnnotationPresent(bannedAnnotation.getKey())) {
throw new IllegalStateException("Do not use "
+ bannedAnnotation.getKey().getCanonicalName()
+ " when using DeviceState, replace with "
+ bannedAnnotation.getValue().getCanonicalName());
}
}
if (method.getAnnotation(BeforeClass.class) != null
|| method.getAnnotation(AfterClass.class) != null) {
checkPublicStaticVoidNoArgs(method);
}
}
}
private void checkPublicStaticVoidNoArgs(Method method) {
if (method.getParameterTypes().length > 0) {
throw new IllegalStateException(
"Method " + method.getName() + " should have no parameters");
}
if (method.getReturnType() != Void.TYPE) {
throw new IllegalStateException("Method " + method.getName() + "() should be void");
}
if (!Modifier.isStatic(method.getModifiers())) {
throw new IllegalStateException(
"Method " + method.getName() + "() should be static");
}
if (!Modifier.isPublic(method.getModifiers())) {
throw new IllegalStateException(
"Method " + method.getName() + "() should be public");
}
}
private void runAnnotatedMethods(
TestClass testClass, Class<? extends Annotation> annotation) throws Throwable {
List<FrameworkMethod> methods = new ArrayList<>(
testClass.getAnnotatedMethods(annotation));
Collections.reverse(methods);
for (FrameworkMethod method : methods) {
try {
method.invokeExplosively(testClass.getJavaClass());
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
}
private void requireRunOnAdditionalUser(OptionalBoolean switchedToUser) {
requireRunOnUser(new String[]{SECONDARY_USER_TYPE_NAME}, switchedToUser);
if (TestApis.users().isHeadlessSystemUserMode()) {
if (TestApis.users().instrumented().equals(TestApis.users().initial())) {
throw new AssumptionViolatedException(
"This test requires running on an additional secondary user");
}
}
}
private void requireRunOnUser(String[] userTypes, OptionalBoolean switchedToUser) {
UserReference instrumentedUser = TestApis.users().instrumented();
assumeTrue("This test only runs on users of type " + Arrays.toString(userTypes),
Arrays.stream(userTypes).anyMatch(
i -> i.equals(instrumentedUser.type().name())));
mUsers.put(instrumentedUser.type(), instrumentedUser);
if (switchedToUser == OptionalBoolean.ANY) {
if (!mAnnotationHasSwitchedUser && instrumentedUser.canBeSwitchedTo()) {
switchedToUser = OptionalBoolean.TRUE;
}
}
if (switchedToUser == OptionalBoolean.TRUE && !instrumentedUser.canBeSwitchedTo()) {
if (TestApis.users().isHeadlessSystemUserMode()
&& instrumentedUser.equals(TestApis.users().system())) {
throw new IllegalStateException(
"Cannot switch to system user on headless devices. "
+ "Either add @RequireNotHeadlessSystemUserMode, or specify "
+ "switchedToUser=ANY");
} else {
throw new IllegalStateException(
"Not permitted to switch to user " + instrumentedUser + "("
+ instrumentedUser.getSwitchToUserError() + ")");
}
}
ensureSwitchedToUser(switchedToUser, instrumentedUser);
}
private void requireRunOnProfile(String userType,
OptionalBoolean installInstrumentedAppInParent,
boolean hasProfileOwner, boolean dpcIsPrimary, boolean useParentInstance,
OptionalBoolean switchedToParentUser, Set<String> affiliationIds,
String dpcKey, TestAppQueryBuilder dpcQuery) {
UserReference instrumentedUser = TestApis.users().instrumented();
assumeTrue("This test only runs on users of type " + userType,
instrumentedUser.type().name().equals(userType));
if (!mProfiles.containsKey(instrumentedUser.type())) {
mProfiles.put(instrumentedUser.type(), new HashMap<>());
}
mProfiles.get(instrumentedUser.type()).put(instrumentedUser.parent(),
instrumentedUser);
if (installInstrumentedAppInParent.equals(OptionalBoolean.TRUE)) {
TestApis.packages().find(sContext.getPackageName()).installExisting(
instrumentedUser.parent());
} else if (installInstrumentedAppInParent.equals(OptionalBoolean.FALSE)) {
TestApis.packages().find(sContext.getPackageName()).uninstall(
instrumentedUser.parent());
}
if (hasProfileOwner) {
ensureHasProfileOwner(
instrumentedUser, dpcIsPrimary, useParentInstance, affiliationIds, dpcKey,
dpcQuery);
} else {
ensureHasNoProfileOwner(instrumentedUser);
}
ensureSwitchedToUser(switchedToParentUser, instrumentedUser.parent());
}
private void ensureSwitchedToUser(OptionalBoolean switchedtoUser, UserReference user) {
if (switchedtoUser.equals(OptionalBoolean.TRUE)) {
mAnnotationHasSwitchedUser = true;
switchToUser(user);
} else if (switchedtoUser.equals(OptionalBoolean.FALSE)) {
mAnnotationHasSwitchedUser = true;
switchFromUser(user);
}
}
private void requireFeature(String feature, FailureMode failureMode) {
checkFailOrSkip("Device must have feature " + feature,
TestApis.packages().features().contains(feature), failureMode);
}
private void requireDoesNotHaveFeature(String feature, FailureMode failureMode) {
checkFailOrSkip("Device must not have feature " + feature,
!TestApis.packages().features().contains(feature), failureMode);
}
private void requireNoPermissionsInstrumentation(String reason) {
boolean instrumentingPermissions =
TestApis.context()
.instrumentedContext().getPackageName()
.equals(mPermissionsInstrumentationPackage);
checkFailOrSkip(
"This test never runs using permissions instrumentation on this version"
+ " of Android: " + reason,
!instrumentingPermissions,
FailureMode.SKIP
);
}
private void requirePermissionsInstrumentation(String reason) {
mHasRequirePermissionInstrumentation = true;
boolean instrumentingPermissions =
TestApis.context()
.instrumentedContext().getPackageName()
.equals(mPermissionsInstrumentationPackage);
checkFailOrSkip(
"This test only runs when using permissions instrumentation on this"
+ " version of Android: " + reason,
instrumentingPermissions,
FailureMode.SKIP
);
}
private void requireTargetSdkVersion(
int min, int max, FailureMode failureMode) {
int targetSdkVersion = TestApis.packages().instrumented().targetSdkVersion();
checkFailOrSkip(
"TargetSdkVersion must be between " + min + " and " + max
+ " (inclusive) (version is " + targetSdkVersion + ")",
min <= targetSdkVersion && max >= targetSdkVersion,
failureMode
);
}
private void requireSdkVersion(int min, int max, FailureMode failureMode) {
requireSdkVersion(min, max, failureMode,
"Sdk version must be between " + min + " and " + max + " (inclusive)");
}
private void requireSdkVersion(
int min, int max, FailureMode failureMode, String failureMessage) {
mMinSdkVersionCurrentTest = min;
checkFailOrSkip(
failureMessage + " (version is " + SDK_INT + ")",
meetsSdkVersionRequirements(min, max),
failureMode
);
}
private com.android.bedstead.nene.users.UserType requireUserSupported(
String userType, FailureMode failureMode) {
com.android.bedstead.nene.users.UserType resolvedUserType =
TestApis.users().supportedType(userType);
checkFailOrSkip(
"Device must support user type " + userType
+ " only supports: " + TestApis.users().supportedTypes(),
resolvedUserType != null, failureMode);
return resolvedUserType;
}
private static final String LOG_TAG = "DeviceState";
private static final Context sContext = TestApis.context().instrumentedContext();
private final Map<com.android.bedstead.nene.users.UserType, UserReference> mUsers =
new HashMap<>();
private final Map<com.android.bedstead.nene.users.UserType, Map<UserReference, UserReference>>
mProfiles = new HashMap<>();
private DevicePolicyController mDeviceOwner;
private UserReference mAdditionalUser = null;
private final Map<UserReference, DevicePolicyController> mProfileOwners = new HashMap<>();
private RemotePolicyManager mDelegateDpc;
private RemotePolicyManager mPrimaryPolicyManager;
private RemoteDevicePolicyManagerRoleHolder mDevicePolicyManagerRoleHolder;
private UserType mOtherUserType;
private PermissionContextImpl mPermissionContext = null;
private final Map<UserReference, Set<String>> mAddedUserRestrictions = new HashMap<>();
private final Map<UserReference, Set<String>> mRemovedUserRestrictions = new HashMap<>();
private final List<UserReference> mCreatedUsers = new ArrayList<>();
private final List<RemovedUser> mRemovedUsers = new ArrayList<>();
private final List<UserReference> mUsersSetPasswords = new ArrayList<>();
private final List<BlockingBroadcastReceiver> mRegisteredBroadcastReceivers = new ArrayList<>();
private boolean mHasChangedDeviceOwner = false;
private DevicePolicyController mOriginalDeviceOwner;
private Integer mOriginalDeviceOwnerType;
private boolean mHasChangedDeviceOwnerType;
private final Map<UserReference, DevicePolicyController> mChangedProfileOwners = new HashMap<>();
private UserReference mOriginalSwitchedUser;
private Boolean mOriginalBluetoothEnabled;
private Boolean mOriginalWifiEnabled;
private final Map<String, Map<String, String>> mOriginalFlagValues = new HashMap<>();
private final TestAppProvider mTestAppProvider = new TestAppProvider();
private final Map<String, TestAppInstance> mTestApps = new HashMap<>();
private final Map<String, String> mOriginalProperties = new HashMap<>();
private final Map<String, String> mOriginalGlobalSettings = new HashMap<>();
private final Map<String, Query> mAdditionalQueryParameters = new HashMap<>();
private final Map<UserReference, Boolean> mOriginalDefaultContentSuggestionsServiceEnabled =
new HashMap<>();
private final Set<UserReference> mTemporaryContentSuggestionsServiceSet =
new HashSet<>();
private final Map<UserReference, Map<String, String>> mOriginalSecureSettings = new HashMap<>();
private boolean mAnnotationHasSwitchedUser = false;
private final Set<AccountReference> mCreatedAccounts = new HashSet<>();
private final Map<String, AccountReference> mAccounts = new HashMap<>();
private final Map<UserReference, RemoteAccountAuthenticator> mAccountAuthenticators =
new HashMap<>();
private static final class RemovedUser {
// Store the user builder so we can recreate the user later
public final UserBuilder userBuilder;
public final boolean isRunning;
public final boolean isOriginalSwitchedToUser;
RemovedUser(UserBuilder userBuilder,
boolean isRunning, boolean isOriginalSwitchedToUser) {
this.userBuilder = userBuilder;
this.isRunning = isRunning;
this.isOriginalSwitchedToUser = isOriginalSwitchedToUser;
}
}
/**
* Get the {@link UserReference} of the work profile for the initial user.
*
* <p>If the current user is a work profile, then the current user will be returned.
*
* <p>This should only be used to get work profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed work profile
*/
public UserReference workProfile() {
return workProfile(/* forUser= */ UserType.INITIAL_USER);
}
/**
* Get the {@link UserReference} of the work profile.
*
* <p>This should only be used to get work profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed work profile for the given user
*/
public UserReference workProfile(UserType forUser) {
return workProfile(resolveUserTypeToUser(forUser));
}
/**
* Get the {@link UserReference} of the work profile.
*
* <p>This should only be used to get work profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed work profile for the given user
*/
public UserReference workProfile(UserReference forUser) {
return profile(MANAGED_PROFILE_TYPE_NAME, forUser);
}
/**
* Get the {@link UserReference} of the profile of the given type for the given user.
*
* <p>This should only be used to get profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed profile for the given user
*/
public UserReference profile(String profileType, UserType forUser) {
return profile(profileType, resolveUserTypeToUser(forUser));
}
/**
* Get the {@link UserReference} of the profile for the current user.
*
* <p>If the current user is a profile of the correct type, then the current user will be
* returned.
*
* <p>This should only be used to get profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed profile
*/
public UserReference profile(String profileType) {
return profile(profileType, /* forUser= */ UserType.INSTRUMENTED_USER);
}
/**
* Get the {@link UserReference} of the profile of the given type for the given user.
*
* <p>This should only be used to get profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed profile for the given user
*/
public UserReference profile(String profileType, UserReference forUser) {
com.android.bedstead.nene.users.UserType resolvedUserType =
TestApis.users().supportedType(profileType);
if (resolvedUserType == null) {
throw new IllegalStateException("Can not have a profile of type " + profileType
+ " as they are not supported on this device");
}
return profile(resolvedUserType, forUser);
}
/**
* Get the {@link UserReference} of the profile of the given type for the given user.
*
* <p>This should only be used to get profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed profile for the given user
*/
public UserReference profile(
com.android.bedstead.nene.users.UserType userType, UserReference forUser) {
if (userType == null || forUser == null) {
throw new NullPointerException();
}
if (!mProfiles.containsKey(userType) || !mProfiles.get(userType).containsKey(forUser)) {
UserReference parentUser = TestApis.users().instrumented().parent();
if (parentUser != null) {
if (mProfiles.containsKey(userType)
&& mProfiles.get(userType).containsKey(parentUser)) {
return mProfiles.get(userType).get(parentUser);
}
}
throw new IllegalStateException(
"No harrier-managed profile of type " + userType
+ ". This method should only"
+ " be used when Harrier has been used to create the profile.");
}
return mProfiles.get(userType).get(forUser);
}
/**
* Get the {@link UserReference} of the tv profile for the current user
*
* <p>This should only be used to get tv profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed tv profile
*/
public UserReference tvProfile() {
return tvProfile(/* forUser= */ UserType.INSTRUMENTED_USER);
}
/**
* Get the {@link UserReference} of the tv profile.
*
* <p>This should only be used to get tv profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed tv profile
*/
public UserReference tvProfile(UserType forUser) {
return tvProfile(resolveUserTypeToUser(forUser));
}
/**
* Get the {@link UserReference} of the tv profile.
*
* <p>This should only be used to get tv profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed tv profile
*/
public UserReference tvProfile(UserReference forUser) {
return profile(TV_PROFILE_TYPE_NAME, forUser);
}
/**
* Get the {@link UserReference} of the clone profile for the current user
*
* <p>This should only be used to get clone profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed clone profile
*/
public UserReference cloneProfile() {
return cloneProfile(/* forUser= */ UserType.INITIAL_USER);
}
/**
* Get the {@link UserReference} of the clone profile.
*
* <p>This should only be used to get clone profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed clone profile
*/
public UserReference cloneProfile(UserType forUser) {
return cloneProfile(resolveUserTypeToUser(forUser));
}
/**
* Get the {@link UserReference} of the clone profile.
*
* <p>This should only be used to get clone profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed clone profile
*/
public UserReference cloneProfile(UserReference forUser) {
return profile(CLONE_PROFILE_TYPE_NAME, forUser);
}
/**
* Get the {@link UserReference} of the private profile for the current user
*
* <p>This should only be used to get private profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed private profile
*/
public UserReference privateProfile() {
return privateProfile(/* forUser= */ UserType.INITIAL_USER);
}
/**
* Get the {@link UserReference} of the private profile.
*
* <p>This should only be used to get private profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed private profile
*/
public UserReference privateProfile(UserType forUser) {
return privateProfile(resolveUserTypeToUser(forUser));
}
/**
* Get the {@link UserReference} of the private profile.
*
* <p>This should only be used to get private profiles managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed private profile
*/
public UserReference privateProfile(UserReference forUser) {
return profile(PRIVATE_PROFILE_TYPE_NAME, forUser);
}
/**
* Gets the user ID of the initial user.
*/
// TODO(b/249047658): cache the initial user at the start of the run.
public UserReference initialUser() {
return TestApis.users().initial();
}
/**
* Gets the user ID of the first human user on the device.
*
* @deprecated Use {@link #initialUser()} to ensure compatibility with Headless System User
* Mode devices.
*/
@Deprecated
public UserReference primaryUser() {
return TestApis.users().all()
.stream().filter(UserReference::isPrimary).findFirst()
.orElseThrow(IllegalStateException::new);
}
/**
* Get a secondary user.
*
* <p>This should only be used to get secondary users managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed secondary user
*/
public UserReference secondaryUser() {
return user(SECONDARY_USER_TYPE_NAME);
}
/**
* Gets the user marked as "other" by use of the {@code @OtherUser} annotation.
*
* @throws IllegalStateException if there is no "other" user
*/
public UserReference otherUser() {
if (mOtherUserType == null) {
throw new IllegalStateException("No other user specified. Use @OtherUser");
}
return resolveUserTypeToUser(mOtherUserType);
}
/**
* Get a user of the given type.
*
* <p>This should only be used to get users managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed user of the correct type
*/
public UserReference user(String userType) {
com.android.bedstead.nene.users.UserType resolvedUserType =
TestApis.users().supportedType(userType);
if (resolvedUserType == null) {
throw new IllegalStateException("Can not have a user of type " + userType
+ " as they are not supported on this device");
}
return user(resolvedUserType);
}
/**
* Get a user of the given type.
*
* <p>This should only be used to get users managed by Harrier (using either the
* annotations or calls to the {@link DeviceState} class.
*
* @throws IllegalStateException if there is no harrier-managed user of the correct type
*/
public UserReference user(com.android.bedstead.nene.users.UserType userType) {
if (userType == null) {
throw new NullPointerException();
}
if (!mUsers.containsKey(userType)) {
throw new IllegalStateException(
"No harrier-managed user of type " + userType
+ ". This method should only be"
+ " used when Harrier has been used to create the user.");
}
return mUsers.get(userType);
}
private UserReference ensureHasProfile(
String profileType,
OptionalBoolean installInstrumentedApp,
UserType forUser,
boolean hasProfileOwner,
boolean profileOwnerIsPrimary,
boolean useParentInstance,
OptionalBoolean switchedToParentUser,
OptionalBoolean isQuietModeEnabled,
String dpcKey,
TestAppQueryBuilder dpcQuery) {
com.android.bedstead.nene.users.UserType resolvedUserType =
requireUserSupported(profileType, FailureMode.SKIP);
UserReference forUserReference = resolveUserTypeToUser(forUser);
UserReference profile =
TestApis.users().findProfileOfType(resolvedUserType, forUserReference);
if (profile == null) {
if (profileType.equals(MANAGED_PROFILE_TYPE_NAME)) {
// TODO(b/239961027): either remove this check (once tests on UserManagerTest /
// MultipleUsersOnMultipleDisplaysTest uses non-work profiles) or add a unit test
// for it on DeviceStateTest
requireFeature(FEATURE_MANAGED_USERS, FailureMode.SKIP);
// DO + work profile isn't a valid state
ensureHasNoDeviceOwner();
ensureDoesNotHaveUserRestriction(DISALLOW_ADD_MANAGED_PROFILE, forUserReference);
}
profile = createProfile(resolvedUserType, forUserReference);
}
profile.start();
if (isQuietModeEnabled == OptionalBoolean.TRUE) {
profile.setQuietMode(true);
} else if (isQuietModeEnabled == OptionalBoolean.FALSE) {
profile.setQuietMode(false);
}
if (installInstrumentedApp.equals(OptionalBoolean.TRUE)) {
TestApis.packages().find(sContext.getPackageName()).installExisting(
profile);
} else if (installInstrumentedApp.equals(OptionalBoolean.FALSE)) {
TestApis.packages().find(sContext.getPackageName()).uninstall(profile);
}
if (!mProfiles.containsKey(resolvedUserType)) {
mProfiles.put(resolvedUserType, new HashMap<>());
}
mProfiles.get(resolvedUserType).put(forUserReference, profile);
if (hasProfileOwner) {
ensureHasProfileOwner(
profile, profileOwnerIsPrimary,
useParentInstance,
/* affiliationIds= */ null,
dpcKey,
dpcQuery);
}
ensureSwitchedToUser(switchedToParentUser, forUserReference);
return profile;
}
private void ensureHasNoProfile(String profileType, UserType forUser) {
UserReference forUserReference = resolveUserTypeToUser(forUser);
com.android.bedstead.nene.users.UserType resolvedProfileType =
TestApis.users().supportedType(profileType);
if (resolvedProfileType == null) {
// These profile types don't exist so there can't be any
return;
}
UserReference profile =
TestApis.users().findProfileOfType(
resolvedProfileType,
forUserReference);
if (profile != null) {
// We can't remove an organization owned profile
ProfileOwner profileOwner = TestApis.devicePolicy().getProfileOwner(profile);
if (profileOwner != null && profileOwner.isOrganizationOwned()) {
profileOwner.setIsOrganizationOwned(false);
}
removeAndRecordUser(profile);
}
}
private void ensureHasUser(
String userType, OptionalBoolean installInstrumentedApp,
OptionalBoolean switchedToUser) {
com.android.bedstead.nene.users.UserType resolvedUserType =
requireUserSupported(userType, FailureMode.SKIP);
Collection<UserReference> users = TestApis.users().findUsersOfType(resolvedUserType);
UserReference user = users.isEmpty() ? createUser(resolvedUserType)
: users.iterator().next();
user.start();
if (installInstrumentedApp.equals(OptionalBoolean.TRUE)) {
TestApis.packages().find(sContext.getPackageName()).installExisting(user);
} else if (installInstrumentedApp.equals(OptionalBoolean.FALSE)) {
TestApis.packages().find(sContext.getPackageName()).uninstall(user);
}
ensureSwitchedToUser(switchedToUser, user);
mUsers.put(resolvedUserType, user);
}
private void ensureHasNoAdditionalUser() {
mAdditionalUser = additionalUserOrNull();
while (mAdditionalUser != null) {
if (TestApis.users().instrumented().equals(mAdditionalUser)) {
throw new AssumptionViolatedException("Tests with @EnsureHasNoAdditionalUser cannot"
+ "run on an additional user");
}
ensureSwitchedToUser(OptionalBoolean.FALSE, mAdditionalUser);
mAdditionalUser.remove();
mAdditionalUser = additionalUserOrNull();
}
}
private void ensureHasAdditionalUser(
OptionalBoolean installInstrumentedApp, OptionalBoolean switchedToUser) {
if (TestApis.users().isHeadlessSystemUserMode()) {
com.android.bedstead.nene.users.UserType resolvedUserType =
requireUserSupported(SECONDARY_USER_TYPE_NAME, FailureMode.SKIP);
Collection<UserReference> users = TestApis.users().findUsersOfType(resolvedUserType);
if (users.size() < 2) {
createUser(resolvedUserType);
}
mAdditionalUser = additionalUserOrNull();
if (installInstrumentedApp.equals(OptionalBoolean.TRUE)) {
TestApis.packages().find(sContext.getPackageName()).installExisting(mAdditionalUser);
} else if (installInstrumentedApp.equals(OptionalBoolean.FALSE)) {
TestApis.packages().find(sContext.getPackageName()).uninstall(mAdditionalUser);
}
ensureSwitchedToUser(switchedToUser, mAdditionalUser);
} else {
ensureHasUser(SECONDARY_USER_TYPE_NAME, installInstrumentedApp, switchedToUser);
mAdditionalUser = additionalUserOrNull();
}
}
/**
* Ensure that there is no user of the given type.
*/
private void ensureHasNoUser(String userType) {
com.android.bedstead.nene.users.UserType resolvedUserType =
TestApis.users().supportedType(userType);
if (resolvedUserType == null) {
// These user types don't exist so there can't be any
return;
}
for (UserReference secondaryUser : TestApis.users().findUsersOfType(resolvedUserType)) {
if (secondaryUser.equals(TestApis.users().instrumented())) {
throw new AssumptionViolatedException(
"This test only runs on devices without a "
+ userType + " user. But the instrumented user is " + userType);
}
removeAndRecordUser(secondaryUser);
}
}
private void removeAndRecordUser(UserReference userReference) {
if (userReference == null) {
return; // Nothing to remove
}
switchFromUser(userReference);
if (!mCreatedUsers.remove(userReference)) {
mRemovedUsers.add(
new RemovedUser(
TestApis.users().createUser()
.name(userReference.name())
.type(userReference.type())
.parent(userReference.parent()),
userReference.isRunning(),
Objects.equal(mOriginalSwitchedUser, userReference)));
}
userReference.remove();
}
private void ensureCanAddUser() {
ensureCanAddUser(1, FailureMode.SKIP);
}
private void ensureCanAddUser(int number, FailureMode failureMode) {
int maxUsers = getMaxNumberOfUsersSupported();
int currentUsers = TestApis.users().all().size();
// TODO(scottjonathan): Try to remove users until we have space - this will have to take
// into account other users which have been added during the setup of this test.
checkFailOrSkip(
"The device does not have space for "
+ number
+ " additional "
+ "user(s) ("
+ currentUsers
+ " current users, "
+ maxUsers
+ " max users)",
currentUsers + number <= maxUsers,
failureMode);
}
private void ensureCanAddProfile(
com.android.bedstead.nene.users.UserType userType, FailureMode failureMode) {
checkFailOrSkip("the device cannot add more profiles of type " + userType,
TestApis.users().canCreateProfile(userType),
failureMode);
}
/**
* Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
* test has run.
*/
public BlockingBroadcastReceiver registerBroadcastReceiver(String action) {
return registerBroadcastReceiver(action, /* checker= */ null);
}
/**
* Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
* test has run.
*/
public BlockingBroadcastReceiver registerBroadcastReceiver(IntentFilter intentFilter) {
return registerBroadcastReceiver(intentFilter, /* checker= */ null);
}
/**
* Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
* test has run.
*/
public BlockingBroadcastReceiver registerBroadcastReceiver(
String action, Function<Intent, Boolean> checker) {
BlockingBroadcastReceiver broadcastReceiver =
new BlockingBroadcastReceiver(mContext, action, checker);
broadcastReceiver.register();
mRegisteredBroadcastReceivers.add(broadcastReceiver);
return broadcastReceiver;
}
/**
* Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
* test has run.
*/
public BlockingBroadcastReceiver registerBroadcastReceiver(
IntentFilter intentfilter, Function<Intent, Boolean> checker) {
BlockingBroadcastReceiver broadcastReceiver =
new BlockingBroadcastReceiver(mContext, intentfilter, checker);
broadcastReceiver.register();
mRegisteredBroadcastReceivers.add(broadcastReceiver);
return broadcastReceiver;
}
/**
* Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
* test has run.
*/
public BlockingBroadcastReceiver registerBroadcastReceiverForUser(
UserReference user, String action) {
return registerBroadcastReceiverForUser(user, action, /* checker= */ null);
}
/**
* Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
* test has run.
*/
public BlockingBroadcastReceiver registerBroadcastReceiverForUser(
UserReference user, IntentFilter intentFilter) {
return registerBroadcastReceiverForUser(user, intentFilter, /* checker= */ null);
}
/**
* Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
* test has run.
*/
public BlockingBroadcastReceiver registerBroadcastReceiverForUser(
UserReference user, String action, Function<Intent, Boolean> checker) {
try (PermissionContext p =
TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
BlockingBroadcastReceiver broadcastReceiver =
new BlockingBroadcastReceiver(
TestApis.context().androidContextAsUser(user), action, checker);
broadcastReceiver.register();
mRegisteredBroadcastReceivers.add(broadcastReceiver);
return broadcastReceiver;
}
}
/**
* Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
* test has run.
*/
public BlockingBroadcastReceiver registerBroadcastReceiverForUser(
UserReference user, IntentFilter intentFilter, Function<Intent, Boolean> checker) {
try (PermissionContext p =
TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
BlockingBroadcastReceiver broadcastReceiver =
new BlockingBroadcastReceiver(
TestApis.context().androidContextAsUser(user), intentFilter, checker);
broadcastReceiver.register();
mRegisteredBroadcastReceivers.add(broadcastReceiver);
return broadcastReceiver;
}
}
/**
* Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
* test has run.
*/
public BlockingBroadcastReceiver registerBroadcastReceiverForAllUsers(String action) {
return registerBroadcastReceiverForAllUsers(action, /* checker= */ null);
}
/**
* Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
* test has run.
*/
public BlockingBroadcastReceiver registerBroadcastReceiverForAllUsers(
IntentFilter intentFilter) {
return registerBroadcastReceiverForAllUsers(intentFilter, /* checker= */ null);
}
/**
* Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
* test has run.
*/
public BlockingBroadcastReceiver registerBroadcastReceiverForAllUsers(
String action, Function<Intent, Boolean> checker) {
try (PermissionContext p =
TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
BlockingBroadcastReceiver broadcastReceiver =
new BlockingBroadcastReceiver(mContext, action, checker);
broadcastReceiver.registerForAllUsers();
mRegisteredBroadcastReceivers.add(broadcastReceiver);
return broadcastReceiver;
}
}
/**
* Create and register a {@link BlockingBroadcastReceiver} which will be unregistered after the
* test has run.
*/
public BlockingBroadcastReceiver registerBroadcastReceiverForAllUsers(
IntentFilter intentFilter, Function<Intent, Boolean> checker) {
try (PermissionContext p =
TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
BlockingBroadcastReceiver broadcastReceiver =
new BlockingBroadcastReceiver(mContext, intentFilter, checker);
broadcastReceiver.registerForAllUsers();
mRegisteredBroadcastReceivers.add(broadcastReceiver);
return broadcastReceiver;
}
}
private UserReference resolveUserTypeToUser(UserType userType) {
switch (userType) {
case SYSTEM_USER:
return TestApis.users().system();
case INSTRUMENTED_USER:
return TestApis.users().instrumented();
case CURRENT_USER:
return TestApis.users().current();
case PRIMARY_USER:
return primaryUser();
case SECONDARY_USER:
return secondaryUser();
case WORK_PROFILE:
return workProfile();
case TV_PROFILE:
return tvProfile();
case DPC_USER:
return dpc().user();
case INITIAL_USER:
return TestApis.users().initial();
case ADDITIONAL_USER:
return additionalUser();
case CLONE_PROFILE:
return cloneProfile();
case PRIVATE_PROFILE:
return privateProfile();
case ADMIN_USER:
return TestApis.users().all().stream().sorted(
Comparator.comparing(UserReference::id))
.filter(UserReference::isAdmin)
.findFirst().orElseThrow(
() -> new IllegalStateException("No admin user on device"));
case ANY:
throw new IllegalStateException("ANY UserType can not be used here");
default:
throw new IllegalArgumentException("Unknown user type " + userType);
}
}
public UserReference additionalUser() {
if (mAdditionalUser == null) {
throw new IllegalStateException(
"No additional user found. Ensure the correct annotations "
+ "have been used to declare use of additional user.");
}
return mAdditionalUser;
}
private UserReference additionalUserOrNull() {
return TestApis.users()
.findUsersOfType(TestApis.users().supportedType(SECONDARY_USER_TYPE_NAME))
.stream()
.sorted(Comparator.comparing(UserReference::id))
.skip(TestApis.users().isHeadlessSystemUserMode() ? 1 : 0)
.findFirst()
.orElse(null);
}
void teardownNonShareableState() {
mAdditionalQueryParameters.clear();
mProfiles.clear();
mUsers.clear();
mAnnotationHasSwitchedUser = false;
mAdditionalUser = null;
for (Map.Entry<UserReference, Set<String>> userRestrictions
: mAddedUserRestrictions.entrySet()) {
for (String restriction : userRestrictions.getValue()) {
ensureDoesNotHaveUserRestriction(restriction, userRestrictions.getKey());
}
}
for (Map.Entry<UserReference, Set<String>> userRestrictions
: mRemovedUserRestrictions.entrySet()) {
for (String restriction : userRestrictions.getValue()) {
ensureHasUserRestriction(restriction, userRestrictions.getKey());
}
}
mAddedUserRestrictions.clear();
mRemovedUserRestrictions.clear();
for (BlockingBroadcastReceiver broadcastReceiver : mRegisteredBroadcastReceivers) {
broadcastReceiver.unregisterQuietly();
}
mRegisteredBroadcastReceivers.clear();
mDelegateDpc = null;
mPrimaryPolicyManager = null;
mOtherUserType = null;
mTestApps.clear();
mAccounts.clear();
mAccountAuthenticators.clear();
mTestAppProvider.restore();
if (mPermissionContext != null) {
mPermissionContext.close();
mPermissionContext = null;
}
for (Map.Entry<String, Map<String, String>> namespace : mOriginalFlagValues.entrySet()) {
for (Map.Entry<String, String> key : namespace.getValue().entrySet()) {
TestApis.flags().set(namespace.getKey(), key.getKey(), key.getValue());
}
}
mOriginalFlagValues.clear();
mAnnotationExecutors.values().forEach(AnnotationExecutor::teardownNonShareableState);
if (mNextSafetyOperationSet) {
ensurePolicyOperationUnsafe(
CommonDevicePolicy.DevicePolicyOperation.OPERATION_NONE,
CommonDevicePolicy.OperationSafetyReason.OPERATION_SAFETY_REASON_NONE);
mNextSafetyOperationSet = false;
}
}
private final Set<TestAppInstance> mInstalledTestApps = new HashSet<>();
private final Set<TestAppInstance> mUninstalledTestApps = new HashSet<>();
void teardown() {
teardownNonShareableState();
teardownShareableState();
}
private void teardownShareableState() {
if (!mCreatedAccounts.isEmpty()) {
mCreatedAccounts.forEach(AccountReference::remove);
TestApis.devicePolicy().calculateHasIncompatibleAccounts();
}
for (UserReference user : mUsersSetPasswords) {
if (mCreatedUsers.contains(user)) {
continue; // Will be removed anyway
}
user.clearPassword();
}
mUsersSetPasswords.clear();
if (mHasChangedDeviceOwner) {
if (mOriginalDeviceOwner == null) {
if (mDeviceOwner != null) {
mDeviceOwner.remove();
}
} else if (!mOriginalDeviceOwner.equals(mDeviceOwner)) {
if (mDeviceOwner != null) {
mDeviceOwner.remove();
}
ensureHasNoProfileOwner(TestApis.users().system());
TestApis.devicePolicy().setDeviceOwner(
mOriginalDeviceOwner.componentName());
}
if (mOriginalDeviceOwner != null && mOriginalDeviceOwnerType != null) {
((DeviceOwner) mOriginalDeviceOwner).setType(mOriginalDeviceOwnerType);
}
mHasChangedDeviceOwner = false;
mOriginalDeviceOwner = null;
mHasChangedDeviceOwnerType = false;
mOriginalDeviceOwnerType = null;
} else {
// Device owner type changed but the device owner is the same.
if (mHasChangedDeviceOwnerType) {
((DeviceOwner) mDeviceOwner).setType(mOriginalDeviceOwnerType);
mHasChangedDeviceOwnerType = false;
mOriginalDeviceOwnerType = null;
}
}
for (Map.Entry<UserReference, DevicePolicyController> originalProfileOwner :
mChangedProfileOwners.entrySet()) {
ProfileOwner currentProfileOwner =
TestApis.devicePolicy().getProfileOwner(originalProfileOwner.getKey());
if (Objects.equal(currentProfileOwner, originalProfileOwner.getValue())) {
continue; // No need to restore
}
if (currentProfileOwner != null) {
currentProfileOwner.remove();
}
if (originalProfileOwner.getValue() != null) {
TestApis.devicePolicy().setProfileOwner(originalProfileOwner.getKey(),
originalProfileOwner.getValue().componentName());
}
}
mChangedProfileOwners.clear();
if (mDevicePolicyManagerRoleHolder != null) {
TestApis.devicePolicy().unsetDevicePolicyManagementRoleHolder(
mDevicePolicyManagerRoleHolder.testApp().pkg(),
mDevicePolicyManagerRoleHolder.user());
mDevicePolicyManagerRoleHolder = null;
}
UserReference ephemeralUser = null;
UserReference currentUser = TestApis.users().current();
for (UserReference user : mCreatedUsers) {
try {
if (user.equals(currentUser)) {
// user will be removed after switching to mOriginalSwitchedUser below.
user.removeWhenPossible();
ephemeralUser = user;
} else {
user.remove();
}
} catch (NeneException e) {
if (user.exists()) {
// Otherwise it's probably just already removed
throw new NeneException("Could not remove user", e);
}
}
}
mCreatedUsers.clear();
for (RemovedUser removedUser : mRemovedUsers) {
UserReference user = removedUser.userBuilder.create();
if (removedUser.isRunning) {
user.start();
}
if (removedUser.isOriginalSwitchedToUser) {
mOriginalSwitchedUser = user;
}
}
mRemovedUsers.clear();
if (mOriginalSwitchedUser != null) {
if (!mOriginalSwitchedUser.exists()) {
Log.d(LOG_TAG, "Could not switch back to original user "
+ mOriginalSwitchedUser
+ " as it does not exist. Switching to initial instead.");
TestApis.users().initial().switchTo();
} else {
mOriginalSwitchedUser.switchTo();
}
mOriginalSwitchedUser = null;
// wait for ephemeral user to be removed after being switched away
if (ephemeralUser != null) {
Poll.forValue("Ephemeral user exists", ephemeralUser::exists)
.toBeEqualTo(false)
.timeout(Duration.ofMinutes(1))
.errorOnFail()
.await();
}
}
for (TestAppInstance installedTestApp : mInstalledTestApps) {
installedTestApp.uninstall();
}
mInstalledTestApps.clear();
for (TestAppInstance uninstalledTestApp : mUninstalledTestApps) {
uninstalledTestApp.testApp().install(uninstalledTestApp.user());
}
mUninstalledTestApps.clear();
if (mOriginalBluetoothEnabled != null) {
TestApis.bluetooth().setEnabled(mOriginalBluetoothEnabled);
mOriginalBluetoothEnabled = null;
}
if (mOriginalWifiEnabled != null) {
TestApis.wifi().setEnabled(mOriginalWifiEnabled);
mOriginalWifiEnabled = null;
}
for (Map.Entry<UserReference, Boolean> s
: mOriginalDefaultContentSuggestionsServiceEnabled.entrySet()) {
TestApis.content().suggestions().setDefaultServiceEnabled(s.getKey(), s.getValue());
}
mOriginalDefaultContentSuggestionsServiceEnabled.clear();
for (UserReference u : mTemporaryContentSuggestionsServiceSet) {
TestApis.content().suggestions().clearTemporaryService(u);
}
mTemporaryContentSuggestionsServiceSet.clear();
for (Map.Entry<String, String> s : mOriginalGlobalSettings.entrySet()) {
TestApis.settings().global().putString(s.getKey(), s.getValue());
}
mOriginalGlobalSettings.clear();
for (Map.Entry<String, String> s : mOriginalProperties.entrySet()) {
TestApis.properties().set(s.getKey(), s.getValue());
}
mOriginalProperties.clear();
for (Map.Entry<UserReference, Map<String, String>> s : mOriginalSecureSettings.entrySet()) {
for (Map.Entry<String, String> s2 : s.getValue().entrySet()) {
TestApis.settings().secure().putString(s.getKey(), s2.getKey(), s2.getValue());
}
}
mOriginalSecureSettings.clear();
TestApis.activities().clearAllActivities();
mAnnotationExecutors.values().forEach(AnnotationExecutor::teardownShareableState);
}
private UserReference createProfile(
com.android.bedstead.nene.users.UserType profileType, UserReference parent) {
ensureCanAddUser();
ensureCanAddProfile(profileType, FailureMode.SKIP);
if (profileType.name().equals("android.os.usertype.profile.CLONE")) {
// Special case - we can't create a clone profile if this is set
ensureDoesNotHaveUserRestriction(DISALLOW_ADD_CLONE_PROFILE, parent);
} else if (profileType.name().equals("android.os.usertype.profile.PRIVATE")) {
// Special case - we can't create a private profile if this is set
ensureDoesNotHaveUserRestriction(DISALLOW_ADD_PRIVATE_PROFILE, parent);
}
try {
UserReference user = TestApis.users().createUser()
.parent(parent)
.type(profileType)
.createAndStart();
mCreatedUsers.add(user);
return user;
} catch (NeneException e) {
throw new IllegalStateException("Error creating profile of type " + profileType, e);
}
}
private UserReference createUser(com.android.bedstead.nene.users.UserType userType) {
ensureDoesNotHaveUserRestriction(UserManager.DISALLOW_ADD_USER, UserType.ANY);
ensureCanAddUser();
try {
UserReference user = TestApis.users().createUser()
.type(userType)
.createAndStart();
mCreatedUsers.add(user);
return user;
} catch (NeneException e) {
throw new IllegalStateException("Error creating user of type " + userType, e);
}
}
private int getMaxNumberOfUsersSupported() {
try {
return ShellCommand.builder("pm get-max-users")
.validate((output) -> output.startsWith("Maximum supported users:"))
.executeAndParseOutput(
(output) -> Integer.parseInt(output.split(": ", 2)[1]
.trim()));
} catch (AdbException e) {
throw new IllegalStateException("Invalid command output", e);
}
}
private void ensureHasDevicePolicyManagerRoleHolder(UserType onUser, boolean isPrimary) {
UserReference user = resolveUserTypeToUser(onUser);
if (!user.equals(TestApis.users().instrumented())) {
// INTERACT_ACROSS_USERS_FULL is required for RemoteDPC
ensureCanGetPermission(INTERACT_ACROSS_USERS_FULL);
}
ensureHasNoAccounts(UserType.ANY, /* allowPreCreatedAccounts= */ true,
FailureMode.FAIL);
ensureTestAppInstalled(RemoteDevicePolicyManagerRoleHolder.sTestApp, user);
TestApis.devicePolicy().setDevicePolicyManagementRoleHolder(
RemoteDevicePolicyManagerRoleHolder.sTestApp.pkg(), user);
mDevicePolicyManagerRoleHolder =
new RemoteDevicePolicyManagerRoleHolder(
RemoteDevicePolicyManagerRoleHolder.sTestApp, user);
if (isPrimary) {
// We will override the existing primary
if (mPrimaryPolicyManager != null) {
Log.i(LOG_TAG, "Overriding primary policy manager "
+ mPrimaryPolicyManager + " with " + mDevicePolicyManagerRoleHolder);
}
mPrimaryPolicyManager = mDevicePolicyManagerRoleHolder;
}
}
private void ensureHasDelegate(
EnsureHasDelegate.AdminType adminType, List<String> scopes, boolean isPrimary) {
RemotePolicyManager dpc = getDeviceAdmin(adminType);
boolean specifiesAdminType = adminType != EnsureHasDelegate.AdminType.PRIMARY;
boolean currentPrimaryPolicyManagerIsNotDelegator =
!Objects.equal(mPrimaryPolicyManager, dpc);
if (isPrimary && mPrimaryPolicyManager != null
&& (specifiesAdminType || currentPrimaryPolicyManagerIsNotDelegator)) {
throw new IllegalStateException(
"Only one DPC can be marked as primary per test (current primary is "
+ mPrimaryPolicyManager + ")");
}
if (!dpc.user().equals(TestApis.users().instrumented())) {
// INTERACT_ACROSS_USERS_FULL is required for RemoteDPC
ensureCanGetPermission(INTERACT_ACROSS_USERS_FULL);
}
ensureTestAppInstalled(DELEGATE_KEY, RemoteDelegate.sTestApp, dpc.user());
RemoteDelegate delegate = new RemoteDelegate(RemoteDelegate.sTestApp, dpc().user());
dpc.devicePolicyManager().setDelegatedScopes(
dpc.componentName(), delegate.packageName(), scopes);
if (isPrimary) {
mDelegateDpc = dpc;
mPrimaryPolicyManager = delegate;
}
}
private void ensureHasNoDelegate(EnsureHasNoDelegate.AdminType adminType) {
if (adminType == EnsureHasNoDelegate.AdminType.ANY) {
for (UserReference user : TestApis.users().all()) {
ensureTestAppNotInstalled(RemoteDelegate.sTestApp, user);
}
return;
}
RemotePolicyManager dpc =
adminType == EnsureHasNoDelegate.AdminType.PRIMARY ? mPrimaryPolicyManager
: adminType == EnsureHasNoDelegate.AdminType.DEVICE_OWNER
? deviceOwner()
: adminType == EnsureHasNoDelegate.AdminType.PROFILE_OWNER
? profileOwner() : null;
if (dpc == null) {
throw new IllegalStateException("Unknown Admin Type " + adminType);
}
ensureTestAppNotInstalled(RemoteDelegate.sTestApp, dpc.user());
}
private void ensureTestAppInstalled(
String key, Query query, UserType onUser, boolean isPrimary) {
TestApp testApp =
mTestAppProvider.query(query).applyAnnotation(
mAdditionalQueryParameters.getOrDefault(key, null)).get();
TestAppInstance testAppInstance = ensureTestAppInstalled(
key, testApp, resolveUserTypeToUser(onUser));
if (isPrimary) {
if (mPrimaryPolicyManager != null) {
throw new IllegalStateException(
"Only one DPC can be marked as primary per test (current primary is "
+ mPrimaryPolicyManager + ")");
}
mPrimaryPolicyManager = new RemoteTestApp(testAppInstance);
}
}
private void ensureTestAppHasPermission(
String testAppKey, String[] permissions, int minVersion, int maxVersion,
FailureMode failureMode) {
checkTestAppExistsWithKey(testAppKey);
try {
mTestApps.get(testAppKey).permissions()
.withPermissionOnVersionBetween(minVersion, maxVersion, permissions);
} catch (NeneException e) {
if (failureMode.equals(FailureMode.SKIP) && e.getMessage().contains("Cannot grant")
|| e.getMessage().contains("Error granting")) {
failOrSkip(e.getMessage(), FailureMode.SKIP);
} else {
throw e;
}
}
}
private void ensureTestAppDoesNotHavePermission(
String testAppKey, String[] permissions, FailureMode failureMode) {
checkTestAppExistsWithKey(testAppKey);
try {
mTestApps.get(testAppKey).permissions().withoutPermission(permissions);
} catch (NeneException e) {
if (failureMode.equals(FailureMode.SKIP) && e.getMessage().contains("Cannot deny")) {
failOrSkip(e.getMessage(), FailureMode.SKIP);
} else {
throw e;
}
}
}
private void ensureTestAppHasAppOp(
String testAppKey, String[] appOps, int minVersion, int maxVersion) {
checkTestAppExistsWithKey(testAppKey);
mTestApps.get(testAppKey).permissions()
.withAppOpOnVersionBetween(minVersion, maxVersion, appOps);
}
private void checkTestAppExistsWithKey(String testAppKey) {
if (!mTestApps.containsKey(testAppKey)) {
throw new NeneException(
"No testapp with key " + testAppKey + ". Use @EnsureTestAppInstalled."
+ " Valid Test apps: " + mTestApps);
}
}
private RemotePolicyManager getDeviceAdmin(EnsureHasDelegate.AdminType adminType) {
switch (adminType) {
case DEVICE_OWNER:
return deviceOwner();
case PROFILE_OWNER:
return profileOwner();
case PRIMARY:
return dpc();
default:
throw new IllegalStateException("Unknown device admin type " + adminType);
}
}
private TestAppInstance ensureTestAppInstalled(TestApp testApp, UserReference user) {
return ensureTestAppInstalled(/* key= */ null, testApp, user);
}
private TestAppInstance ensureTestAppInstalled(String key, TestApp testApp,
UserReference user) {
if (!mAdditionalQueryParameters.isEmpty()) {
assumeFalse("b/276740719 - we don't support custom delegates",
DELEGATE_KEY.equals(key));
}
Package pkg = TestApis.packages().find(testApp.packageName());
TestAppInstance testAppInstance = null;
if (pkg != null && TestApis.packages().find(testApp.packageName()).installedOnUser(
user)) {
testAppInstance = testApp.instance(user);
} else {
// TODO: Consider if we want to record that we've started it so we can stop it after
// if needed?
user.start();
testAppInstance = testApp.install(user);
mInstalledTestApps.add(testAppInstance);
}
if (key != null) {
mTestApps.put(key, testAppInstance);
}
return testAppInstance;
}
private void ensureTestAppNotInstalled(TestApp testApp, UserReference user) {
Package pkg = TestApis.packages().find(testApp.packageName());
if (pkg == null || !TestApis.packages().find(testApp.packageName()).installedOnUser(
user)) {
return;
}
TestAppInstance instance = testApp.instance(user);
if (mInstalledTestApps.contains(instance)) {
mInstalledTestApps.remove(instance);
} else {
mUninstalledTestApps.add(instance);
}
testApp.uninstall(user);
}
private void ensureHasDeviceOwner(FailureMode failureMode, boolean isPrimary,
EnsureHasDeviceOwner.HeadlessDeviceOwnerType headlessDeviceOwnerType,
Set<String> affiliationIds, int type,
String key, TestAppQueryBuilder dpcQuery) {
// TODO(scottjonathan): Should support non-remotedpc device owner (default to remotedpc)
dpcQuery.applyAnnotation(mAdditionalQueryParameters.getOrDefault(key, null));
if (dpcQuery.isEmptyQuery()) {
dpcQuery = defaultDpcQuery();
}
if (headlessDeviceOwnerType == EnsureHasDeviceOwner.HeadlessDeviceOwnerType.AFFILIATED
&& TestApis.users().isHeadlessSystemUserMode()) {
affiliationIds.add("DEFAULT_AFFILIATED"); // To ensure headless PO + DO are affiliated
}
UserReference userReference = TestApis.users().system();
if (isPrimary && mPrimaryPolicyManager != null && !userReference.equals(
mPrimaryPolicyManager.user())) {
throw new IllegalStateException(
"Only one DPC can be marked as primary per test (current primary is "
+ mPrimaryPolicyManager + ")");
}
if (!userReference.equals(TestApis.users().instrumented())) {
// INTERACT_ACROSS_USERS_FULL is required for RemoteDPC
ensureCanGetPermission(INTERACT_ACROSS_USERS_FULL);
}
DeviceOwner currentDeviceOwner = TestApis.devicePolicy().getDeviceOwner();
// if current device owner matches query, keep it as it is
if (RemoteDpc.matchesRemoteDpcQuery(currentDeviceOwner, dpcQuery)) {
mDeviceOwner = currentDeviceOwner;
} else {
// if there is no device owner, or current device owner is not a remote dpc
UserReference instrumentedUser = TestApis.users().instrumented();
if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) {
// Prior to S we can't set device owner if there are other users on the device
if (instrumentedUser.id() != 0) {
// If we're not on the system user we can't reach the required state
throw new AssumptionViolatedException(
"Can't set Device Owner when running on non-system-user"
+ " on this version of Android");
}
for (UserReference u : TestApis.users().all()) {
if (u.equals(instrumentedUser)) {
// Can't remove the user we're running on
continue;
}
try {
removeAndRecordUser(u);
} catch (NeneException e) {
failOrSkip(
"Error removing user to prepare for DeviceOwner: "
+ e,
failureMode);
}
}
}
// We must remove all non-test users on all devices though
// (except for the first 1 if headless and always the system user)
int allowedNonTestUsers = TestApis.users().isHeadlessSystemUserMode() ? 1 : 0;
UserReference instrumented = TestApis.users().instrumented();
for (UserReference u : TestApis.users().all().stream()
.sorted(Comparator.comparing(u -> u.equals(instrumented)).reversed())
.collect(Collectors.toList())) {
if (u.isSystem()) {
continue;
}
if (u.isForTesting()) {
continue;
}
if (allowedNonTestUsers > 0) {
allowedNonTestUsers--;
continue;
}
if (u.equals(instrumented)) {
// From U+ we limit non-for-testing users when setting device owner.
if (Versions.meetsMinimumSdkVersionRequirement(Versions.U)) {
throw new IllegalStateException(
"Cannot set Device Owner when running on a "
+ "non-for-testing secondary user (" + u + ")");
} else {
continue;
}
}
try {
removeAndRecordUser(u);
} catch (NeneException e) {
failOrSkip(
"Error removing user to prepare for DeviceOwner: "
+ e,
failureMode);
}
}
if (Versions.meetsMinimumSdkVersionRequirement(Versions.U)) {
ensureHasNoAccounts(UserType.ANY, /* allowPreCreatedAccounts= */ true,
FailureMode.FAIL);
} else {
// Prior to U this only checked the system user
ensureHasNoAccounts(UserType.SYSTEM_USER, /* allowPreCreatedAccounts= */ true,
FailureMode.FAIL);
}
ensureHasNoProfileOwner(userReference);
if (!mHasChangedDeviceOwner) {
recordDeviceOwner();
mHasChangedDeviceOwner = true;
mHasChangedDeviceOwnerType = true;
}
mDeviceOwner = RemoteDpc.setAsDeviceOwner(dpcQuery).devicePolicyController();
}
if (isPrimary) {
mPrimaryPolicyManager = RemoteDpc.forDevicePolicyController(mDeviceOwner);
}
int deviceOwnerType = ((DeviceOwner) mDeviceOwner).getType();
if (deviceOwnerType != type) {
if (!mHasChangedDeviceOwnerType) {
mOriginalDeviceOwnerType = deviceOwnerType;
mHasChangedDeviceOwnerType = true;
}
((DeviceOwner) mDeviceOwner).setType(type);
}
if (type != DeviceOwnerType.FINANCED) {
// API is not allowed to be called by a financed device owner.
RemoteDpc remoteDpcForDeviceOwner = RemoteDpc.forDevicePolicyController(mDeviceOwner);
remoteDpcForDeviceOwner.devicePolicyManager()
.setAffiliationIds(
remoteDpcForDeviceOwner.componentName(),
affiliationIds);
}
if (headlessDeviceOwnerType == EnsureHasDeviceOwner.HeadlessDeviceOwnerType.AFFILIATED
&& TestApis.users().isHeadlessSystemUserMode()) {
// To simulate "affiliated" headless mode - we must also set the profile owner on the
// initial user
ensureHasProfileOwner(TestApis.users().initial(),
/* isPrimary= */ false, /* useParentInstance= */ false,
affiliationIds, key, dpcQuery,
RemoteDpc.forDevicePolicyController(mDeviceOwner).testApp());
}
}
private void recordDeviceOwner() {
mOriginalDeviceOwner = TestApis.devicePolicy().getDeviceOwner();
mOriginalDeviceOwnerType =
mOriginalDeviceOwner != null ? ((DeviceOwner) mOriginalDeviceOwner).getType()
: null;
}
private void ensureHasProfileOwner(UserType onUser, boolean isPrimary,
boolean useParentInstance, Set<String> affiliationIds, String key,
TestAppQueryBuilder dpcQuery) {
// TODO(scottjonathan): Should support non-remotedpc profile owner
// (default to remotedpc)
UserReference user = resolveUserTypeToUser(onUser);
ensureHasProfileOwner(user, isPrimary, useParentInstance, affiliationIds, key, dpcQuery);
}
private void ensureHasProfileOwner(
UserReference user, boolean isPrimary, boolean useParentInstance,
Set<String> affiliationIds, String key, TestAppQueryBuilder dpcQuery) {
ensureHasProfileOwner(user, isPrimary, useParentInstance, affiliationIds, key, dpcQuery,
/* resolvedDpcTestApp= */ null);
}
private void ensureHasProfileOwner(
UserReference user, boolean isPrimary, boolean useParentInstance,
Set<String> affiliationIds, String key,
TestAppQueryBuilder dpcQuery, TestApp resolvedDpcTestApp) {
dpcQuery.applyAnnotation(mAdditionalQueryParameters.getOrDefault(key, null));
if (dpcQuery.isEmptyQuery()) {
dpcQuery = defaultDpcQuery();
}
if (isPrimary && mPrimaryPolicyManager != null
&& !user.equals(mPrimaryPolicyManager.user())) {
throw new IllegalStateException(
"Only one DPC can be marked as primary per test");
}
if (!user.equals(TestApis.users().instrumented())) {
// INTERACT_ACROSS_USERS_FULL is required for RemoteDPC
ensureCanGetPermission(INTERACT_ACROSS_USERS_FULL);
}
ProfileOwner currentProfileOwner = TestApis.devicePolicy().getProfileOwner(user);
DeviceOwner currentDeviceOwner = TestApis.devicePolicy().getDeviceOwner();
if (currentDeviceOwner != null && currentDeviceOwner.user().equals(user)) {
// Can't have DO and PO on the same user
ensureHasNoDeviceOwner();
}
if (RemoteDpc.matchesRemoteDpcQuery(currentProfileOwner, dpcQuery)) {
mProfileOwners.put(user, currentProfileOwner);
} else {
if (user.parent() != null) {
ensureDoesNotHaveUserRestriction(DISALLOW_ADD_MANAGED_PROFILE, user.parent());
}
if (!mChangedProfileOwners.containsKey(user)) {
mChangedProfileOwners.put(user, currentProfileOwner);
}
ensureHasNoAccounts(user, /* allowPreCreatedAccounts= */ true,
FailureMode.FAIL);
if (resolvedDpcTestApp != null) {
mProfileOwners.put(user,
RemoteDpc.setAsProfileOwner(user, resolvedDpcTestApp)
.devicePolicyController());
} else {
mProfileOwners.put(user,
RemoteDpc.setAsProfileOwner(user, dpcQuery).devicePolicyController());
}
}
if (Versions.meetsMinimumSdkVersionRequirement(Versions.U)) {
ensureHasNoAccounts(user, /* allowPreCreatedAccounts= */ true,
FailureMode.FAIL);
} else {
// Prior to U this incorrectly checked the system user
ensureHasNoAccounts(UserType.SYSTEM_USER, /* allowPreCreatedAccounts= */ true,
FailureMode.FAIL);
}
if (isPrimary) {
if (useParentInstance) {
mPrimaryPolicyManager = new RemoteDpcUsingParentInstance(
RemoteDpc.forDevicePolicyController(mProfileOwners.get(user)));
} else {
mPrimaryPolicyManager =
RemoteDpc.forDevicePolicyController(mProfileOwners.get(user));
}
}
if (affiliationIds != null) {
RemoteDpc profileOwner = profileOwner(user);
profileOwner.devicePolicyManager()
.setAffiliationIds(profileOwner.componentName(), affiliationIds);
}
}
private void ensureHasNoDeviceOwner() {
DeviceOwner deviceOwner = TestApis.devicePolicy().getDeviceOwner();
if (deviceOwner == null) {
return;
}
if (!mHasChangedDeviceOwner) {
recordDeviceOwner();
mHasChangedDeviceOwner = true;
mHasChangedDeviceOwnerType = true;
}
mDeviceOwner = null;
deviceOwner.remove();
}
private void ensureHasNoProfileOwner(UserType onUser) {
UserReference user = resolveUserTypeToUser(onUser);
ensureHasNoProfileOwner(user);
}
private void ensureHasNoProfileOwner(UserReference user) {
ProfileOwner currentProfileOwner = TestApis.devicePolicy().getProfileOwner(user);
if (currentProfileOwner == null) {
return;
}
if (!mChangedProfileOwners.containsKey(user)) {
mChangedProfileOwners.put(user, currentProfileOwner);
}
TestApis.devicePolicy().getProfileOwner(user).remove();
mProfileOwners.remove(user);
}
/**
* Get the {@link RemoteDpc} for the device owner controlled by Harrier.
*
* <p>If no Harrier-managed device owner exists, an exception will be thrown.
*
* <p>If the device owner is not a RemoteDPC then an exception will be thrown
*/
public RemoteDpc deviceOwner() {
if (mDeviceOwner == null) {
throw new IllegalStateException(
"No Harrier-managed device owner. This method should "
+ "only be used when Harrier was used to set the Device Owner.");
}
if (!RemoteDpc.isRemoteDpc(mDeviceOwner)) {
throw new IllegalStateException("The device owner is not a RemoteDPC."
+ " You must use Nene to query for this device owner.");
}
return RemoteDpc.forDevicePolicyController(mDeviceOwner);
}
/**
* Get the {@link RemoteDpc} for the profile owner on the current user controlled by Harrier.
*
* <p>If no Harrier-managed profile owner exists, an exception will be thrown.
*
* <p>If the profile owner is not a RemoteDPC then an exception will be thrown.
*/
public RemoteDpc profileOwner() {
return profileOwner(UserType.INSTRUMENTED_USER);
}
/**
* Get the {@link RemoteDpc} for the profile owner on the given user controlled by Harrier.
*
* <p>If no Harrier-managed profile owner exists, an exception will be thrown.
*
* <p>If the profile owner is not a RemoteDPC then an exception will be thrown.
*/
public RemoteDpc profileOwner(UserType onUser) {
if (onUser == null) {
throw new NullPointerException();
}
return profileOwner(resolveUserTypeToUser(onUser));
}
/**
* Get the {@link RemoteDpc} for the profile owner on the given user controlled by Harrier.
*
* <p>If no Harrier-managed profile owner exists, an exception will be thrown.
*
* <p>If the profile owner is not a RemoteDPC then an exception will be thrown.
*/
public RemoteDpc profileOwner(UserReference onUser) {
if (onUser == null) {
throw new NullPointerException();
}
if (!mProfileOwners.containsKey(onUser)) {
throw new IllegalStateException(
"No Harrier-managed profile owner. This method should "
+ "only be used when Harrier was used to set the Profile Owner.");
}
DevicePolicyController profileOwner = mProfileOwners.get(onUser);
if (!RemoteDpc.isRemoteDpc(profileOwner)) {
throw new IllegalStateException("The profile owner is not a RemoteDPC."
+ " You must use Nene to query for this profile owner.");
}
return RemoteDpc.forDevicePolicyController(profileOwner);
}
private void requirePackageInstalled(
String packageName, UserType forUser, FailureMode failureMode) {
Package pkg = TestApis.packages().find(packageName);
if (forUser.equals(UserType.ANY)) {
checkFailOrSkip(
packageName + " is required to be installed",
!pkg.installedOnUsers().isEmpty(),
failureMode);
} else {
checkFailOrSkip(
packageName + " is required to be installed for " + forUser,
pkg.installedOnUser(resolveUserTypeToUser(forUser)),
failureMode);
}
}
private void requirePackageNotInstalled(
String packageName, UserType forUser, FailureMode failureMode) {
Package pkg = TestApis.packages().find(packageName);
if (forUser.equals(UserType.ANY)) {
checkFailOrSkip(
packageName + " is required to be not installed",
pkg.installedOnUsers().isEmpty(),
failureMode);
} else {
checkFailOrSkip(
packageName + " is required to be not installed for " + forUser,
!pkg.installedOnUser(resolveUserTypeToUser(forUser)),
failureMode);
}
}
private void ensurePackageNotInstalled(
String packageName, UserType forUser) {
Package pkg = TestApis.packages().find(packageName);
if (forUser.equals(UserType.ANY)) {
pkg.uninstallFromAllUsers();
} else {
UserReference user = resolveUserTypeToUser(forUser);
pkg.uninstall(user);
}
}
/**
* Behaves like {@link #dpc()} except that when running on a delegate, this will return
* the delegating DPC not the delegate.
*/
public RemotePolicyManager dpcOnly() {
if (mPrimaryPolicyManager != null) {
if (mPrimaryPolicyManager.isDelegate()) {
return mDelegateDpc;
}
}
return dpc();
}
/**
* Get the most appropriate {@link RemotePolicyManager} instance for the device state.
*
* <p>This method should only be used by tests which are annotated with {@link PolicyTest}.
*
* <p>This may be a DPC, a delegate, or a normal app with or without given permissions.
*
* <p>If no policy manager is set as "primary" for the device state, then this method will first
* check for a profile owner in the current user, or else check for a device owner.
*
* <p>If no Harrier-managed profile owner or device owner exists, an exception will be thrown.
*
* <p>If the profile owner or device owner is not a RemoteDPC then an exception will be thrown.
*/
public RemotePolicyManager dpc() {
if (mPrimaryPolicyManager != null) {
return mPrimaryPolicyManager;
}
if (mProfileOwners.containsKey(TestApis.users().instrumented())) {
DevicePolicyController profileOwner =
mProfileOwners.get(TestApis.users().instrumented());
if (RemoteDpc.isRemoteDpc(profileOwner)) {
return RemoteDpc.forDevicePolicyController(profileOwner);
}
}
if (mDeviceOwner != null) {
if (RemoteDpc.isRemoteDpc(mDeviceOwner)) {
return RemoteDpc.forDevicePolicyController(mDeviceOwner);
}
}
throw new IllegalStateException("No Harrier-managed profile owner or device owner. "
+ "Ensure you have set up the DPC using bedstead annotations.");
}
/**
* Get the Device Policy Management Role Holder.
*/
public RemoteDevicePolicyManagerRoleHolder dpmRoleHolder() {
if (mDevicePolicyManagerRoleHolder == null) {
throw new IllegalStateException(
"No Harrier-managed device policy manager role holder.");
}
return mDevicePolicyManagerRoleHolder;
}
/**
* Get a {@link TestAppProvider} which is cleared between tests.
*
* <p>Note that you must still manage the test apps manually. To have the infrastructure
* automatically remove test apps use the {@link EnsureTestAppInstalled} annotation.
*/
public TestAppProvider testApps() {
return mTestAppProvider;
}
/**
* Get a test app installed with @EnsureTestAppInstalled with no key.
*/
public TestAppInstance testApp() {
return testApp(DEFAULT_KEY);
}
/**
* Get a test app installed with `@EnsureTestAppInstalled` with the given key.
*/
public TestAppInstance testApp(String key) {
if (!mTestApps.containsKey(key)) {
throw new NeneException("No testapp with given key. Use @EnsureTestAppInstalled");
}
return mTestApps.get(key);
}
private void ensureCanGetPermission(String permission) {
if (mPermissionsInstrumentationPackage == null) {
// We just need to check if we can get it generally
if (TestApis.permissions().usablePermissions().contains(permission)) {
return;
}
if (TestApis.packages().instrumented().isInstantApp()) {
// Instant Apps aren't able to know the permissions of shell so we can't know
// if we
// can adopt it - we'll assume we can adopt and log
Log.i(LOG_TAG,
"Assuming we can get permission " + permission
+ " as running on instant app");
return;
}
TestApis.permissions().throwPermissionException(
"Can not get required permission", permission);
}
if (TestApis.permissions().adoptablePermissions().contains(permission)) {
requireNoPermissionsInstrumentation("Requires permission " + permission);
} else if (mPermissionsInstrumentationPackagePermissions.contains(permission)) {
requirePermissionsInstrumentation("Requires permission " + permission);
} else {
// Can't get permission at all - error (including the permissions for both)
TestApis.permissions().throwPermissionException(
"Can not get permission " + permission + " including by instrumenting "
+ mPermissionsInstrumentationPackage
+ "\n " + mPermissionsInstrumentationPackage + " permissions: "
+ mPermissionsInstrumentationPackagePermissions,
permission
);
}
}
private void switchToUser(UserReference user) {
UserReference currentUser = TestApis.users().current();
if (!currentUser.equals(user)) {
if (mOriginalSwitchedUser == null) {
mOriginalSwitchedUser = currentUser;
}
user.switchTo();
}
}
private void switchFromUser(UserReference user) {
UserReference currentUser = TestApis.users().current();
if (!currentUser.equals(user)) {
return;
}
// We need to find a different user to switch to
// full users only, starting with lowest ID
List<UserReference> users = new ArrayList<>(TestApis.users().all());
users.sort(Comparator.comparingInt(UserReference::id));
for (UserReference otherUser : users) {
if (otherUser.equals(user)) {
continue;
}
if (otherUser.parent() != null) {
continue;
}
if (!otherUser.isRunning()) {
continue;
}
if (!otherUser.canBeSwitchedTo()) {
continue;
}
switchToUser(otherUser);
return;
}
// There are no users to switch to so we'll create one.
// In HSUM, an additional user needs to be created to switch from the existing user.
ensureHasAdditionalUser(/* installInstrumentedApp= */ OptionalBoolean.ANY,
/* switchedToUser= */ OptionalBoolean.TRUE);
}
private void requireNotHeadlessSystemUserMode(String reason) {
assumeFalse(reason, TestApis.users().isHeadlessSystemUserMode());
}
private void requireHeadlessSystemUserMode(String reason) {
assumeTrue(reason, TestApis.users().isHeadlessSystemUserMode());
}
private void requireLowRamDevice(String reason, FailureMode failureMode) {
checkFailOrSkip(reason,
TestApis.context().instrumentedContext()
.getSystemService(ActivityManager.class)
.isLowRamDevice(),
failureMode);
}
private void requireNotLowRamDevice(String reason, FailureMode failureMode) {
checkFailOrSkip(reason,
!TestApis.context().instrumentedContext()
.getSystemService(ActivityManager.class)
.isLowRamDevice(),
failureMode);
}
private void requireVisibleBackgroundUsersSupported(String reason, FailureMode failureMode) {
if (!TestApis.users().isVisibleBackgroundUsersSupported()) {
String message = "Device does not support visible background users, but test requires "
+ "it. Reason: " + reason;
failOrSkip(message, failureMode);
}
}
private void requireVisibleBackgroundUsersNotSupported(String reason, FailureMode failureMode) {
if (TestApis.users().isVisibleBackgroundUsersSupported()) {
String message = "Device supports visible background users, but test requires that it "
+ "doesn't. Reason: " + reason;
failOrSkip(message, failureMode);
}
}
private void requireVisibleBackgroundUsersOnDefaultDisplaySupported(String reason,
FailureMode failureMode) {
if (!TestApis.users().isVisibleBackgroundUsersOnDefaultDisplaySupported()) {
String message = "Device does not support visible background users on default display, "
+ "but test requires it. Reason: " + reason;
failOrSkip(message, failureMode);
}
}
private void requireVisibleBackgroundUsersOnDefaultDisplayNotSupported(String reason,
FailureMode failureMode) {
if (TestApis.users().isVisibleBackgroundUsersOnDefaultDisplaySupported()) {
String message = "Device supports visible background users on default display, but test"
+ " requires that it doesn't. Reason: " + reason;
failOrSkip(message, failureMode);
}
}
private boolean isNonProfileUserRunningVisibleOnBackground() {
UserReference user = TestApis.users().instrumented();
boolean isIt = user.isVisibleBagroundNonProfileUser();
Log.d(LOG_TAG, "isNonProfileUserRunningVisibleOnBackground(" + user + "): " + isIt);
return isIt;
}
private void ensureScreenIsOn() {
TestApis.device().wakeUp();
}
private void ensureUnlocked() {
TestApis.device().unlock();
}
private void ensurePasswordSet(UserType forUser, String password) {
UserReference user = resolveUserTypeToUser(forUser);
if (user.hasLockCredential()) {
return;
}
try {
user.setPassword(password);
} catch (NeneException e) {
throw new AssertionError("Require password set but error when setting "
+ "password on user " + user, e);
}
mUsersSetPasswords.add(user);
}
private void ensurePasswordNotSet(UserType forUser) {
UserReference user = resolveUserTypeToUser(forUser);
if (!user.hasLockCredential()) {
return;
}
if (mUsersSetPasswords.contains(user)) {
try {
user.clearPassword();
} catch (NeneException e) {
Log.e(LOG_TAG, "Error clearing password", e);
}
}
if (!user.hasLockCredential()) {
return;
}
try {
user.clearPassword(DEFAULT_PASSWORD);
} catch (NeneException e) {
throw new AssertionError(
"Test requires user " + user + " does not have a password. "
+ "Password is set and is not DEFAULT_PASSWORD.", e);
}
mUsersSetPasswords.remove(user);
}
private void ensureBluetoothEnabled() {
// TODO(b/220306133): bluetooth from background
Assume.assumeTrue("Can only configure bluetooth from foreground",
TestApis.users().instrumented().isForeground());
ensureDoesNotHaveUserRestriction(DISALLOW_BLUETOOTH, UserType.ANY);
if (mOriginalBluetoothEnabled == null) {
mOriginalBluetoothEnabled = TestApis.bluetooth().isEnabled();
}
TestApis.bluetooth().setEnabled(true);
}
private void ensureBluetoothDisabled() {
Assume.assumeTrue("Can only configure bluetooth from foreground",
TestApis.users().instrumented().isForeground());
if (mOriginalBluetoothEnabled == null) {
mOriginalBluetoothEnabled = TestApis.bluetooth().isEnabled();
}
TestApis.bluetooth().setEnabled(false);
}
private void ensureWifiEnabled() {
if (mOriginalWifiEnabled == null) {
mOriginalWifiEnabled = TestApis.wifi().isEnabled();
}
TestApis.wifi().setEnabled(true);
}
private void ensureWifiDisabled() {
if (mOriginalWifiEnabled == null) {
mOriginalWifiEnabled = TestApis.wifi().isEnabled();
}
TestApis.wifi().setEnabled(false);
}
private boolean isOrganizationOwned(Annotation annotation)
throws InvocationTargetException, IllegalAccessException {
Method isOrganizationOwnedMethod;
try {
isOrganizationOwnedMethod = annotation.annotationType().getMethod(
"isOrganizationOwned");
} catch (NoSuchMethodException ignored) {
return false;
}
return (boolean) isOrganizationOwnedMethod.invoke(annotation);
}
private void withAppOp(String... appOp) {
if (mPermissionContext == null) {
mPermissionContext = TestApis.permissions().withAppOp(appOp);
} else {
mPermissionContext = mPermissionContext.withAppOp(appOp);
}
}
private void withoutAppOp(String... appOp) {
if (mPermissionContext == null) {
mPermissionContext = TestApis.permissions().withoutAppOp(appOp);
} else {
mPermissionContext = mPermissionContext.withoutAppOp(appOp);
}
}
private void withPermission(String... permission) {
if (mPermissionContext == null) {
mPermissionContext = TestApis.permissions().withPermission(permission);
} else {
mPermissionContext = mPermissionContext.withPermission(permission);
}
}
private void withoutPermission(String... permission) {
requireNotInstantApp("Uses withoutPermission", FailureMode.SKIP);
if (mPermissionContext == null) {
mPermissionContext = TestApis.permissions().withoutPermission(permission);
} else {
mPermissionContext = mPermissionContext.withoutPermission(permission);
}
}
private void ensureGlobalSettingSet(String key, String value) {
if (!mOriginalGlobalSettings.containsKey(key)) {
mOriginalGlobalSettings.put(key, TestApis.settings().global().getString(key));
}
TestApis.settings().global().putString(key, value);
}
private void ensureSecureSettingSet(UserType user, String key, String value) {
ensureSecureSettingSet(resolveUserTypeToUser(user), key, value);
}
private void ensureSecureSettingSet(UserReference user, String key, String value) {
if (!mOriginalSecureSettings.containsKey(user)) {
mOriginalSecureSettings.put(user, new HashMap<>());
}
if (!mOriginalSecureSettings.get(user).containsKey(key)) {
mOriginalSecureSettings.get(user)
.put(key, TestApis.settings().secure().getString(user, key));
}
TestApis.settings().secure().putString(user, key, value);
}
private void ensureDefaultContentSuggestionsServiceEnabled(UserType user, boolean enabled) {
ensureDefaultContentSuggestionsServiceEnabled(resolveUserTypeToUser(user), enabled);
}
private void ensureDefaultContentSuggestionsServiceEnabled(UserReference user,
boolean enabled) {
boolean currentValue = TestApis.content().suggestions().defaultServiceEnabled(user);
if (currentValue == enabled) {
return;
}
if (!mOriginalDefaultContentSuggestionsServiceEnabled.containsKey(user)) {
mOriginalDefaultContentSuggestionsServiceEnabled.put(user, currentValue);
}
TestApis.content().suggestions().setDefaultServiceEnabled(enabled);
}
private final TestApp mContentTestApp = testApps().query().wherePackageName()
.isEqualTo("com.android.ContentTestApp").get();
// TODO: Use queries
private final ComponentReference mContentSuggestionsService =
ComponentReference.unflattenFromString(
"com.android.ContentTestApp/.ContentSuggestionsService");
private void ensureHasTestContentSuggestionsService(UserType user) {
ensureHasTestContentSuggestionsService(resolveUserTypeToUser(user));
}
private void ensureHasTestContentSuggestionsService(UserReference user) {
ensureDefaultContentSuggestionsServiceEnabled(user, /* enabled= */ false);
TestAppInstance contentTestApp =
ensureTestAppInstalled("content", mContentTestApp, user);
mTemporaryContentSuggestionsServiceSet.add(user);
TestApis.content().suggestions().setTemporaryService(user, mContentSuggestionsService);
}
private void requireMultiUserSupport(FailureMode failureMode) {
checkFailOrSkip("This test is only supported on multi user devices",
TestApis.users().supportsMultipleUsers(), failureMode);
}
private void requireHasPolicyExemptApps(FailureMode failureMode) {
checkFailOrSkip("OEM does not define any policy-exempt apps",
!TestApis.devicePolicy().getPolicyExemptApps().isEmpty(), failureMode);
}
private void requireInstantApp(String reason, FailureMode failureMode) {
checkFailOrSkip("Test only runs as an instant-app: " + reason,
TestApis.packages().instrumented().isInstantApp(), failureMode);
}
private void requireNotInstantApp(String reason, FailureMode failureMode) {
checkFailOrSkip("Test does not run as an instant-app: " + reason,
!TestApis.packages().instrumented().isInstantApp(), failureMode);
}
private void requireFeatureFlagEnabled(String namespace, String key, FailureMode failureMode) {
//TODO(b/274439760): Get rid of this special case when tidying up permission stuff.
String coexistenceOption = TestApis.instrumentation().arguments()
.getString("COEXISTENCE", "?");
// This is to allow for forcing the coexistence flags on in btest.
if (namespace == NAMESPACE_DEVICE_POLICY_MANAGER
&& (key == PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG
|| key == ENABLE_DEVICE_POLICY_ENGINE_FLAG)) {
if (coexistenceOption.equals("true")) {
// Forcing the flags on will happen later, so we should skip the check below.
return;
} else if (coexistenceOption.equals("false")) {
// Definitely fail or skip if the coexistence flags are being forced off.
failOrSkip("Feature flag " + namespace + ":" + key + " must be enabled",
failureMode);
}
}
checkFailOrSkip("Feature flag " + namespace + ":" + key + " must be enabled",
TestApis.flags().isEnabled(namespace, key), failureMode);
}
private void ensureFeatureFlagEnabled(String namespace, String key) {
ensureFeatureFlagValue(namespace, key, Flags.ENABLED_VALUE);
}
private void requireFeatureFlagNotEnabled(
String namespace, String key, FailureMode failureMode) {
//TODO(b/274439760): Get rid of this special case when tidying up permission stuff.
String coexistenceOption = TestApis.instrumentation().arguments()
.getString("COEXISTENCE", "?");
if (namespace == NAMESPACE_DEVICE_POLICY_MANAGER
&& (key == PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG
|| key == ENABLE_DEVICE_POLICY_ENGINE_FLAG)) {
if (coexistenceOption.equals("false")) {
// Forcing the flags off will happen later, so we should skip the check below.
return;
} else if (coexistenceOption.equals("true")) {
// Definitely fail or skip if the coexistence flags are being forced on.
failOrSkip("Feature flag " + namespace + ":" + key + " must be enabled",
failureMode);
}
}
checkFailOrSkip("Feature flag " + namespace + ":" + key + " must not be enabled",
!TestApis.flags().isEnabled(namespace, key), failureMode);
}
private void ensureFeatureFlagNotEnabled(String namespace, String key) {
ensureFeatureFlagValue(namespace, key, Flags.DISABLED_VALUE);
}
private void requireFeatureFlagValue(
String namespace, String key, String value, FailureMode failureMode) {
checkFailOrSkip("Feature flag " + namespace + ":" + key + " must be enabled",
Objects.equal(value, TestApis.flags().get(namespace, key)), failureMode);
}
private void ensureFeatureFlagValue(String namespace, String key, String value) {
Map<String, String> originalNamespace =
mOriginalFlagValues.computeIfAbsent(namespace, k -> new HashMap<>());
if (!originalNamespace.containsKey(key)) {
originalNamespace.put(key, TestApis.flags().get(namespace, key));
}
TestApis.flags().set(namespace, key, value);
}
/**
* Access harrier-managed accounts on the instrumented user.
*/
public RemoteAccountAuthenticator accounts() {
return accounts(TestApis.users().instrumented());
}
/**
* Access harrier-managed accounts on the given user.
*/
public RemoteAccountAuthenticator accounts(UserType user) {
return accounts(resolveUserTypeToUser(user));
}
/**
* Access harrier-managed accounts on the given user.
*/
public RemoteAccountAuthenticator accounts(UserReference user) {
if (!mAccountAuthenticators.containsKey(user)) {
throw new IllegalStateException("No Harrier-Managed account authenticator on user "
+ user + ". Did you use @EnsureHasAccountAuthenticator or @EnsureHasAccount?");
}
return mAccountAuthenticators.get(user);
}
private void ensureHasAccountAuthenticator(UserType onUser) {
UserReference user = resolveUserTypeToUser(onUser);
// We don't use .install() so we can rely on the default testapp sharing/uninstall logic
ensureTestAppInstalled(REMOTE_ACCOUNT_AUTHENTICATOR_TEST_APP,
user);
mAccountAuthenticators.put(user, RemoteAccountAuthenticator.install(user));
}
private void ensureHasAccount(UserType onUser, String key, String[] features) {
ensureHasAccount(onUser, key, features, new HashSet<>());
}
private AccountReference ensureHasAccount(UserType onUser, String key, String[] features,
Set<AccountReference> ignoredAccounts) {
ensureHasAccountAuthenticator(onUser);
Optional<AccountReference> account =
accounts(onUser).allAccounts().stream().filter(i -> !ignoredAccounts.contains(i))
.findFirst();
if (account.isPresent()) {
accounts(onUser).setFeatures(account.get(), Set.of(features));
mAccounts.put(key, account.get());
TestApis.devicePolicy().calculateHasIncompatibleAccounts();
return account.get();
}
AccountReference createdAccount = accounts(onUser).addAccount()
.features(Set.of(features))
.add();
mCreatedAccounts.add(createdAccount);
mAccounts.put(key, createdAccount);
TestApis.devicePolicy().calculateHasIncompatibleAccounts();
return createdAccount;
}
private void ensureHasAccounts(EnsureHasAccount[] accounts) {
Set<AccountReference> ignoredAccounts = new HashSet<>();
for (EnsureHasAccount account : accounts) {
ignoredAccounts.add(ensureHasAccount(
account.onUser(), account.key(), account.features(), ignoredAccounts));
}
}
private void ensureHasNoAccounts(UserType userType, boolean allowPreCreatedAccounts,
FailureMode failureMode) {
if (userType == UserType.ANY) {
TestApis.users().all().forEach(user -> ensureHasNoAccounts(user,
allowPreCreatedAccounts, failureMode));
} else {
ensureHasNoAccounts(resolveUserTypeToUser(userType),
allowPreCreatedAccounts, failureMode);
}
}
private void ensureHasNoAccounts(UserReference user, boolean allowPreCreatedAccounts,
FailureMode failureMode) {
if (REMOTE_ACCOUNT_AUTHENTICATOR_TEST_APP.pkg().installedOnUser(user)) {
user.start(); // The user has to be started to remove accounts
RemoteAccountAuthenticator.install(user).allAccounts()
.forEach(AccountReference::remove);
}
Set<AccountReference> accounts = TestApis.accounts().all(user);
// If allowPreCreatedAccounts is enabled, that means it's okay to have
// pre created accounts on the device.
// Now to EnsureHasNoAccounts we will only check that there are no non-pre created accounts.
// Non pre created accounts either have ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED
// or do not have ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED
if (allowPreCreatedAccounts) {
accounts = accounts.stream()
.filter(accountReference -> !accountReference.hasFeature(
DevicePolicyManager.ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED)
|| accountReference.hasFeature(
DevicePolicyManager.ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED))
.collect(Collectors.toSet());
}
if (!accounts.isEmpty()) {
failOrSkip("Expected no user created accounts on user " + user
+ " but there was some that could not be removed.", failureMode);
}
TestApis.devicePolicy().calculateHasIncompatibleAccounts();
}
/**
* Get the default account defined with {@link EnsureHasAccount}.
*/
public AccountReference account() {
return account(DEFAULT_ACCOUNT_KEY);
}
/**
* Get the account defined with {@link EnsureHasAccount} with a given key.
*/
public AccountReference account(String key) {
if (!mAccounts.containsKey(key)) {
throw new IllegalStateException("No account for key " + key);
}
return mAccounts.get(key);
}
@Override
boolean isHeadlessSystemUserMode() {
return TestApis.users().isHeadlessSystemUserMode();
}
private final Map<Class<? extends AnnotationExecutor>, AnnotationExecutor>
mAnnotationExecutors = new HashMap<>();
private AnnotationExecutor getAnnotationExecutor(
Class<? extends AnnotationExecutor> annotationExecutorClass) {
if (!mAnnotationExecutors.containsKey(annotationExecutorClass)) {
try {
mAnnotationExecutors.put(
annotationExecutorClass, annotationExecutorClass.newInstance());
} catch (Exception e) {
throw new RuntimeException("Error creating annotation executor", e);
}
}
return mAnnotationExecutors.get(annotationExecutorClass);
}
private void ensureHasUserRestriction(String restriction, UserType onUser) {
ensureHasUserRestriction(restriction, resolveUserTypeToUser(onUser));
}
private void ensureHasUserRestriction(String restriction, UserReference onUser) {
if (TestApis.devicePolicy().userRestrictions(onUser).isSet(restriction)) {
return;
}
boolean shouldRunAsRoot = shouldRunAsRoot();
if (shouldRunAsRoot) {
Log.i(LOG_TAG, "Trying to set user restriction as root.");
try {
TestApis.devicePolicy().userRestrictions(onUser).set(restriction,
/* set= */ true);
} catch (AdbException e) {
Log.i(LOG_TAG,
"Unable to set user restriction as root, trying to set using heuristics.");
trySetUserRestriction(onUser, restriction);
}
} else {
trySetUserRestriction(onUser, restriction);
}
if (!TestApis.devicePolicy().userRestrictions(onUser).isSet(restriction)) {
String message =
"Infra cannot set user restriction " + restriction + (shouldRunAsRoot ? ""
: ". Please add RequireAdbRoot to enable root capabilities.");
throw new AssumptionViolatedException(message);
}
if (mRemovedUserRestrictions.containsKey(onUser)
&& mRemovedUserRestrictions.get(onUser).contains(restriction)) {
mRemovedUserRestrictions.get(onUser).remove(restriction);
} else {
if (!mAddedUserRestrictions.containsKey(onUser)) {
mAddedUserRestrictions.put(onUser, new HashSet<>());
}
mAddedUserRestrictions.get(onUser).add(restriction);
}
if (!TestApis.devicePolicy().userRestrictions(onUser).isSet(restriction)) {
throw new NeneException("Error setting user restriction " + restriction);
}
}
private void trySetUserRestriction(UserReference onUser, String restriction) {
Log.i(LOG_TAG, "Trying to set user restriction using heuristics.");
boolean hasSet = false;
if (onUser.equals(TestApis.users().system())) {
hasSet = trySetUserRestrictionWithDeviceOwner(restriction);
}
if (!hasSet) {
hasSet = trySetUserRestrictionWithProfileOwner(onUser, restriction);
}
if (!hasSet && !onUser.equals(TestApis.users().system())) {
trySetUserRestrictionWithDeviceOwner(restriction);
}
}
private boolean trySetUserRestrictionWithDeviceOwner(String restriction) {
ensureHasDeviceOwner(FailureMode.FAIL,
/* isPrimary= */ false, EnsureHasDeviceOwner.HeadlessDeviceOwnerType.NONE,
/* affiliationIds= */ Set.of(), /* type= */ DeviceOwnerType.DEFAULT,
EnsureHasDeviceOwner.DEFAULT_KEY, new TestAppProvider().query());
RemotePolicyManager dpc = deviceOwner();
try {
dpc.devicePolicyManager().addUserRestriction(dpc.componentName(), restriction);
} catch (SecurityException e) {
if (e.getMessage().contains("cannot set user restriction")) {
return false;
}
throw e;
}
return true;
}
private boolean trySetUserRestrictionWithProfileOwner(UserReference onUser,
String restriction) {
ensureHasProfileOwner(onUser,
/* isPrimary= */ false, /* isParentInstance= */ false,
/* affiliationIds= */ Set.of(), EnsureHasProfileOwnerKt.DEFAULT_KEY,
new TestAppProvider().query());
RemotePolicyManager dpc = profileOwner(onUser);
try {
dpc.devicePolicyManager().addUserRestriction(dpc.componentName(), restriction);
} catch (SecurityException e) {
if (e.getMessage().contains("cannot set user restriction")) {
return false;
}
throw e;
}
return true;
}
private boolean tryClearUserRestrictionWithDeviceOwner(String restriction) {
ensureHasDeviceOwner(FailureMode.FAIL,
/* isPrimary= */ false, EnsureHasDeviceOwner.HeadlessDeviceOwnerType.NONE,
/* affiliationIds= */ Set.of(), /* type= */ DeviceOwnerType.DEFAULT,
EnsureHasDeviceOwner.DEFAULT_KEY, new TestAppProvider().query());
RemotePolicyManager dpc = deviceOwner();
try {
dpc.devicePolicyManager().clearUserRestriction(dpc.componentName(), restriction);
} catch (SecurityException e) {
if (e.getMessage().contains("cannot set user restriction")) {
return false;
}
throw e;
}
return true;
}
private boolean tryClearUserRestrictionWithProfileOwner(UserReference onUser,
String restriction) {
ensureHasProfileOwner(onUser,
/* isPrimary= */ false, /* isParentInstance= */ false,
/* affiliationIds= */ Set.of(), EnsureHasProfileOwnerKt.DEFAULT_KEY,
new TestAppProvider().query());
RemotePolicyManager dpc = profileOwner(onUser);
try {
dpc.devicePolicyManager().clearUserRestriction(dpc.componentName(), restriction);
} catch (SecurityException e) {
if (e.getMessage().contains("cannot set user restriction")) {
return false;
}
throw e;
}
return true;
}
private void ensureDoesNotHaveUserRestriction(String restriction, UserType onUser) {
if (onUser == UserType.ANY) {
for (UserReference userReference : TestApis.users().all()) {
ensureDoesNotHaveUserRestriction(restriction, userReference);
}
return;
}
ensureDoesNotHaveUserRestriction(restriction, resolveUserTypeToUser(onUser));
}
private void tryClearUserRestriction(UserReference onUser, String restriction) {
if (restriction.equals(DISALLOW_ADD_MANAGED_PROFILE)) {
// Special case - set by the system whenever there is a Device Owner
ensureHasNoDeviceOwner();
} else if (restriction.equals(DISALLOW_ADD_CLONE_PROFILE)) {
// Special case - set by the system whenever there is a Device Owner
ensureHasNoDeviceOwner();
} else if (restriction.equals(DISALLOW_ADD_PRIVATE_PROFILE)) {
// Special case - set by the system whenever there is a Device Owner
ensureHasNoDeviceOwner();
} else if (restriction.equals(DISALLOW_ADD_USER)) {
// Special case - set by the system whenever there is a Device Owner or
// organization-owned profile owner
ensureHasNoDeviceOwner();
ProfileOwner orgOwnedProfileOwner =
TestApis.devicePolicy().getOrganizationOwnedProfileOwner();
if (orgOwnedProfileOwner != null) {
ensureHasNoProfileOwner(orgOwnedProfileOwner.user());
return;
}
}
boolean hasCleared = !TestApis.devicePolicy().userRestrictions(onUser).isSet(
restriction);
if (!hasCleared && onUser.equals(TestApis.users().system())) {
hasCleared = tryClearUserRestrictionWithDeviceOwner(restriction);
}
if (!hasCleared) {
hasCleared = tryClearUserRestrictionWithProfileOwner(onUser, restriction);
}
if (!hasCleared && !onUser.equals(TestApis.users().system())) {
tryClearUserRestrictionWithDeviceOwner(restriction);
}
}
private void ensureDoesNotHaveUserRestriction(String restriction, UserReference onUser) {
if (!TestApis.devicePolicy().userRestrictions(onUser).isSet(restriction)) {
return;
}
boolean shouldRunAsRoot = shouldRunAsRoot();
if (shouldRunAsRoot) {
Log.i(LOG_TAG, "Trying to clear user restriction as root.");
try {
TestApis.devicePolicy().userRestrictions(onUser).set(restriction,
/* set= */ false);
} catch (AdbException e) {
Log.i(LOG_TAG,
"Unable to clear user restriction as root, trying to clear using heuristics.",
e);
tryClearUserRestriction(onUser, restriction);
}
} else {
tryClearUserRestriction(onUser, restriction);
}
if (TestApis.devicePolicy().userRestrictions(onUser).isSet(restriction)) {
String message =
"Infra cannot remove user restriction " + restriction + (shouldRunAsRoot ? ""
: ". If this test requires capabilities only available on devices "
+ "where adb has root, add @RequireAdbRoot to the test.");
throw new AssumptionViolatedException(message);
}
if (mAddedUserRestrictions.containsKey(onUser)
&& mAddedUserRestrictions.get(onUser).contains(restriction)) {
mAddedUserRestrictions.get(onUser).remove(restriction);
} else {
if (!mRemovedUserRestrictions.containsKey(onUser)) {
mRemovedUserRestrictions.put(onUser, new HashSet<>());
}
mRemovedUserRestrictions.get(onUser).add(restriction);
}
}
private void requireSystemServiceAvailable(Class<?> serviceClass, FailureMode failureMode) {
checkFailOrSkip("Requires " + serviceClass + " to be available",
TestApis.services().serviceIsAvailable(serviceClass), failureMode);
}
private void requireStorageEncryptionSupported() {
checkFailOrSkip("Requires storage encryption to be supported.",
TestApis.devicePolicy().getStorageEncryptionStatus() != ENCRYPTION_STATUS_UNSUPPORTED,
FailureMode.SKIP);
}
private void requireAdbOverWifi(FailureMode failureMode) {
String message = "The test requires adb to be connected over wifi.\n "
+ "Use the below commands to do this: \n"
+ "adb tcpip 5555 \n"
+ "adb connect ip-address-of-device:5555\n";
checkFailOrSkip(message, TestApis.adb().isEnabledOverWifi(), failureMode);
}
private void requireStorageEncryptionUnsupported() {
checkFailOrSkip("Requires storage encryption to not be supported.",
TestApis.devicePolicy().getStorageEncryptionStatus()
== ENCRYPTION_STATUS_UNSUPPORTED,
FailureMode.SKIP);
}
private void requireAdbRoot(FailureMode failureMode) {
if (TestApis.adb().isRootAvailable()) {
Tags.addTag(Tags.ADB_ROOT);
} else {
failOrSkip("Device does not have root available.", failureMode);
}
}
private static boolean shouldRunAsRoot() {
return Tags.hasTag(Tags.ADB_ROOT);
}
private void ensurePolicyOperationUnsafe(
CommonDevicePolicy.DevicePolicyOperation operation,
CommonDevicePolicy.OperationSafetyReason reason) {
mNextSafetyOperationSet = true;
TestApis.devicePolicy().setNextOperationSafety(operation, reason);
mNextSafetyOperationSet = true;
TestApis.devicePolicy().setNextOperationSafety(operation, reason);
}
private void requireFactoryResetProtectionPolicySupported() {
checkFailOrSkip("Requires factory reset protection policy to be supported",
TestApis.devicePolicy().isFactoryResetProtectionPolicySupported(),
FailureMode.FAIL);
}
private void ensurePropertySet(String key, String value) {
if (!mOriginalProperties.containsKey(key)) {
mOriginalProperties.put(key, TestApis.properties().get(key));
}
TestApis.properties().set(key, value);
}
}