Home | History | Annotate | Download | only in devicepolicy
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.cts.devicepolicy;
     18 
     19 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
     20 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
     21 import com.android.ddmlib.testrunner.TestResult.TestStatus;
     22 import com.android.tradefed.build.IBuildInfo;
     23 import com.android.tradefed.config.Option;
     24 import com.android.tradefed.device.CollectingOutputReceiver;
     25 import com.android.tradefed.device.DeviceNotAvailableException;
     26 import com.android.tradefed.log.LogUtil.CLog;
     27 import com.android.tradefed.result.CollectingTestListener;
     28 import com.android.tradefed.result.FileInputStreamSource;
     29 import com.android.tradefed.result.LogDataType;
     30 import com.android.tradefed.result.TestDescription;
     31 import com.android.tradefed.result.TestResult;
     32 import com.android.tradefed.result.TestRunResult;
     33 import com.android.tradefed.testtype.DeviceTestCase;
     34 import com.android.tradefed.testtype.IBuildReceiver;
     35 import com.android.tradefed.util.FileUtil;
     36 import com.android.tradefed.util.TarUtil;
     37 
     38 import java.io.File;
     39 import java.io.FileNotFoundException;
     40 import java.io.IOException;
     41 import java.util.ArrayList;
     42 import java.util.Arrays;
     43 import java.util.Collections;
     44 import java.util.HashSet;
     45 import java.util.List;
     46 import java.util.Map;
     47 import java.util.Set;
     48 import java.util.concurrent.TimeUnit;
     49 
     50 import javax.annotation.Nullable;
     51 
     52 /**
     53  * Base class for device policy tests. It offers utility methods to run tests, set device or profile
     54  * owner, etc.
     55  */
     56 public class BaseDevicePolicyTest extends DeviceTestCase implements IBuildReceiver {
     57 
     58     @Option(
     59             name = "skip-device-admin-feature-check",
     60             description = "Flag that allows to skip the check for android.software.device_admin "
     61                 + "and run the tests no matter what. This is useful for system that do not what "
     62                 + "to expose that feature publicly."
     63     )
     64     private boolean mSkipDeviceAdminFeatureCheck = false;
     65 
     66     private static final String RUNNER = "androidx.test.runner.AndroidJUnitRunner";
     67 
     68     protected static final int USER_SYSTEM = 0; // From the UserHandle class.
     69 
     70     protected static final int USER_OWNER = 0;
     71 
     72     private static final long TIMEOUT_USER_REMOVED_MILLIS = TimeUnit.SECONDS.toMillis(15);
     73     private static final long WAIT_SAMPLE_INTERVAL_MILLIS = 200;
     74 
     75     /**
     76      * The defined timeout (in milliseconds) is used as a maximum waiting time when expecting the
     77      * command output from the device. At any time, if the shell command does not output anything
     78      * for a period longer than defined timeout the Tradefed run terminates.
     79      */
     80     private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(20);
     81 
     82     /** instrumentation test runner argument key used for individual test timeout */
     83     protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
     84 
     85     /**
     86      * Sets timeout (in milliseconds) that will be applied to each test. In the
     87      * event of a test timeout it will log the results and proceed with executing the next test.
     88      */
     89     private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
     90 
     91     /**
     92      * The amount of milliseconds to wait for the remove user calls in {@link #tearDown}.
     93      * This is a temporary measure until b/114057686 is fixed.
     94      */
     95     private static final long USER_REMOVE_WAIT = TimeUnit.SECONDS.toMillis(5);
     96 
     97     // From the UserInfo class
     98     protected static final int FLAG_PRIMARY = 0x00000001;
     99     protected static final int FLAG_GUEST = 0x00000004;
    100     protected static final int FLAG_EPHEMERAL = 0x00000100;
    101     protected static final int FLAG_MANAGED_PROFILE = 0x00000020;
    102 
    103     /**
    104      * The {@link android.os.BatteryManager} flags value representing all charging types; {@link
    105      * android.os.BatteryManager#BATTERY_PLUGGED_AC}, {@link
    106      * android.os.BatteryManager#BATTERY_PLUGGED_USB}, and {@link
    107      * android.os.BatteryManager#BATTERY_PLUGGED_WIRELESS}.
    108      */
    109     private static final int STAY_ON_WHILE_PLUGGED_IN_FLAGS = 7;
    110 
    111     protected static interface Settings {
    112         public static final String GLOBAL_NAMESPACE = "global";
    113         public static interface Global {
    114             public static final String DEVICE_PROVISIONED = "device_provisioned";
    115         }
    116     }
    117 
    118     protected IBuildInfo mCtsBuild;
    119     protected CompatibilityBuildHelper mBuildHelper;
    120     private String mPackageVerifier;
    121     private HashSet<String> mAvailableFeatures;
    122 
    123     /** Packages installed as part of the tests */
    124     private Set<String> mFixedPackages;
    125 
    126     /** Whether DPM is supported. */
    127     protected boolean mHasFeature;
    128     protected int mPrimaryUserId;
    129 
    130     /** Whether multi-user is supported. */
    131     protected boolean mSupportsMultiUser;
    132 
    133     /** Whether file-based encryption (FBE) is supported. */
    134     protected boolean mSupportsFbe;
    135 
    136     /** Whether the device has a lock screen.*/
    137     protected boolean mHasSecureLockScreen;
    138 
    139     /** Users we shouldn't delete in the tests */
    140     private ArrayList<Integer> mFixedUsers;
    141 
    142     private static final String VERIFY_CREDENTIAL_CONFIRMATION = "Lock credential verified";
    143 
    144     @Override
    145     public void setBuild(IBuildInfo buildInfo) {
    146         mCtsBuild = buildInfo;
    147     }
    148 
    149     @Override
    150     protected void setUp() throws Exception {
    151         super.setUp();
    152         assertNotNull(mCtsBuild);  // ensure build has been set before test is run.
    153         mHasFeature = getDevice().getApiLevel() >= 21; /* Build.VERSION_CODES.L */
    154         if (!mSkipDeviceAdminFeatureCheck) {
    155             mHasFeature = mHasFeature && hasDeviceFeature("android.software.device_admin");
    156         }
    157         mSupportsMultiUser = getMaxNumberOfUsersSupported() > 1;
    158         mSupportsFbe = hasDeviceFeature("android.software.file_based_encryption");
    159         mFixedPackages = getDevice().getInstalledPackageNames();
    160         mBuildHelper = new CompatibilityBuildHelper(mCtsBuild);
    161 
    162         mHasSecureLockScreen = hasDeviceFeature("android.software.secure_lock_screen");
    163 
    164         // disable the package verifier to avoid the dialog when installing an app
    165         mPackageVerifier = getDevice().executeShellCommand(
    166                 "settings get global package_verifier_enable");
    167         getDevice().executeShellCommand("settings put global package_verifier_enable 0");
    168 
    169         mFixedUsers = new ArrayList<>();
    170         mPrimaryUserId = getPrimaryUser();
    171         mFixedUsers.add(mPrimaryUserId);
    172         if (mPrimaryUserId != USER_SYSTEM) {
    173             mFixedUsers.add(USER_SYSTEM);
    174         }
    175 
    176         if (mHasFeature) {
    177             // Switching to primary is only needed when we're testing device admin features.
    178             switchUser(mPrimaryUserId);
    179         } else {
    180             // Otherwise, all the tests can be executed in any of the Android users, so remain in
    181             // current user, and don't delete it. This enables testing in secondary users.
    182             if (getDevice().getCurrentUser() != mPrimaryUserId) {
    183                 mFixedUsers.add(getDevice().getCurrentUser());
    184             }
    185         }
    186 
    187         removeOwners();
    188         removeTestUsers();
    189         // Unlock keyguard before test
    190         wakeupAndDismissKeyguard();
    191         stayAwake();
    192         // Go to home.
    193         executeShellCommand("input keyevent KEYCODE_HOME");
    194     }
    195 
    196     @Override
    197     protected void tearDown() throws Exception {
    198         // reset the package verifier setting to its original value
    199         getDevice().executeShellCommand("settings put global package_verifier_enable "
    200                 + mPackageVerifier);
    201         removeOwners();
    202         removeTestUsers();
    203         removeTestPackages();
    204         super.tearDown();
    205     }
    206 
    207     protected void installAppAsUser(String appFileName, int userId) throws FileNotFoundException,
    208             DeviceNotAvailableException {
    209         installAppAsUser(appFileName, true, userId);
    210     }
    211 
    212     protected void installAppAsUser(String appFileName, boolean grantPermissions, int userId)
    213             throws FileNotFoundException, DeviceNotAvailableException {
    214         CLog.d("Installing app " + appFileName + " for user " + userId);
    215         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
    216         String result = getDevice().installPackageForUser(
    217                 buildHelper.getTestFile(appFileName), true, grantPermissions, userId, "-t");
    218         assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result,
    219                 result);
    220     }
    221 
    222     protected void forceStopPackageForUser(String packageName, int userId) throws Exception {
    223         // TODO Move this logic to ITestDevice
    224         executeShellCommand("am force-stop --user " + userId + " " + packageName);
    225     }
    226 
    227     protected void executeShellCommand(final String command) throws Exception {
    228         CLog.d("Starting command " + command);
    229         String commandOutput = getDevice().executeShellCommand(command);
    230         CLog.d("Output for command " + command + ": " + commandOutput);
    231     }
    232 
    233     /** Initializes the user with the given id. This is required so that apps can run on it. */
    234     protected void startUser(int userId) throws Exception {
    235         getDevice().startUser(userId);
    236     }
    237 
    238     /**
    239      * Starts switching to the user with the given ID.
    240      *
    241      * <p>This is not blocking. Some operations will be flaky if called immediately afterwards, such
    242      * as {@link #wakeupAndDismissKeyguard()}. Call {@link #waitForBroadcastIdle()} between this
    243      * method and those operations to ensure that switching the user has finished.
    244      */
    245     protected void switchUser(int userId) throws Exception {
    246         // TODO Move this logic to ITestDevice
    247         executeShellCommand("am switch-user " + userId);
    248     }
    249 
    250     protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
    251         return getDevice().getMaxNumberOfUsersSupported();
    252     }
    253 
    254     protected int getMaxNumberOfRunningUsersSupported() throws DeviceNotAvailableException {
    255         return getDevice().getMaxNumberOfRunningUsersSupported();
    256     }
    257 
    258     protected int getUserFlags(int userId) throws DeviceNotAvailableException {
    259         String command = "pm list users";
    260         String commandOutput = getDevice().executeShellCommand(command);
    261         CLog.i("Output for command " + command + ": " + commandOutput);
    262 
    263         String[] lines = commandOutput.split("\\r?\\n");
    264         assertTrue(commandOutput + " should contain at least one line", lines.length >= 1);
    265         for (int i = 1; i < lines.length; i++) {
    266             // Individual user is printed out like this:
    267             // \tUserInfo{$id$:$name$:$Integer.toHexString(flags)$} [running]
    268             String[] tokens = lines[i].split("\\{|\\}|:");
    269             assertTrue(lines[i] + " doesn't contain 4 or 5 tokens",
    270                     tokens.length == 4 || tokens.length == 5);
    271             // If the user IDs match, return the flags.
    272             if (Integer.parseInt(tokens[1]) == userId) {
    273                 return Integer.parseInt(tokens[3], 16);
    274             }
    275         }
    276         fail("User not found");
    277         return 0;
    278     }
    279 
    280     protected ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
    281         return getDevice().listUsers();
    282     }
    283 
    284     protected  ArrayList<Integer> listRunningUsers() throws DeviceNotAvailableException {
    285         ArrayList<Integer> runningUsers = new ArrayList<>();
    286         for (int userId : listUsers()) {
    287             if (getDevice().isUserRunning(userId)) {
    288                 runningUsers.add(userId);
    289             }
    290         }
    291         return runningUsers;
    292     }
    293 
    294     protected int getFirstManagedProfileUserId() throws DeviceNotAvailableException {
    295         for (int userId : listUsers()) {
    296             if ((getUserFlags(userId) & FLAG_MANAGED_PROFILE) != 0) {
    297                 return userId;
    298             }
    299         }
    300         fail("Managed profile not found");
    301         return 0;
    302     }
    303 
    304     private void stopUserAsync(int userId) throws Exception {
    305         String stopUserCommand = "am stop-user -f " + userId;
    306         CLog.d("starting command \"" + stopUserCommand);
    307         CLog.d("Output for command " + stopUserCommand + ": "
    308                 + getDevice().executeShellCommand(stopUserCommand));
    309     }
    310 
    311     protected void stopUser(int userId) throws Exception {
    312         String stopUserCommand = "am stop-user -w -f " + userId;
    313         CLog.d("starting command \"" + stopUserCommand + "\" and waiting.");
    314         CLog.d("Output for command " + stopUserCommand + ": "
    315                 + getDevice().executeShellCommand(stopUserCommand));
    316     }
    317 
    318     protected void waitForBroadcastIdle() throws DeviceNotAvailableException, IOException {
    319         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
    320         try {
    321             // we allow 8min for the command to complete and 4min for the command to start to
    322             // output something
    323             getDevice().executeShellCommand(
    324                     "am wait-for-broadcast-idle", receiver, 8, 4, TimeUnit.MINUTES, 0);
    325         } finally {
    326             String output = receiver.getOutput();
    327             CLog.d("Output from 'am wait-for-broadcast-idle': %s", output);
    328             if (!output.contains("All broadcast queues are idle!")) {
    329                 // Gather the system_server dump data for investigation.
    330                 File heapDump = getDevice().dumpHeap("system_server", "/data/local/tmp/dump.hprof");
    331                 if (heapDump != null) {
    332                     // If file is too too big, tar if with TarUtil.
    333                     String pid = getDevice().getProcessPid("system_server");
    334                     // gzip the file it's quite big
    335                     File heapDumpGz = TarUtil.gzip(heapDump);
    336                     try (FileInputStreamSource source = new FileInputStreamSource(heapDumpGz)) {
    337                         addTestLog(
    338                                 String.format("system_server_dump.%s.%s.hprof",
    339                                         pid, getDevice().getDeviceDate()),
    340                                 LogDataType.GZIP, source);
    341                     } finally {
    342                         FileUtil.deleteFile(heapDump);
    343                     }
    344                 } else {
    345                     CLog.e("Failed to capture the dumpheap from system_server");
    346                 }
    347                 // the call most likely failed we should fail the test
    348                 fail("'am wait-for-broadcase-idle' did not complete.");
    349                 // TODO: consider adding a reboot or recovery before failing if necessary
    350             }
    351         }
    352     }
    353 
    354     protected void removeUser(int userId) throws Exception  {
    355         if (listUsers().contains(userId) && userId != USER_SYSTEM) {
    356             // Don't log output, as tests sometimes set no debug user restriction, which
    357             // causes this to fail, we should still continue and remove the user.
    358             String stopUserCommand = "am stop-user -w -f " + userId;
    359             CLog.d("stopping and removing user " + userId);
    360             getDevice().executeShellCommand(stopUserCommand);
    361             // TODO: Remove both sleeps and USER_REMOVE_WAIT constant when b/114057686 is fixed.
    362             Thread.sleep(USER_REMOVE_WAIT);
    363             // Ephemeral users may have already been removed after being stopped.
    364             if (listUsers().contains(userId)) {
    365                 assertTrue("Couldn't remove user", getDevice().removeUser(userId));
    366                 Thread.sleep(USER_REMOVE_WAIT);
    367             }
    368         }
    369     }
    370 
    371     protected void removeTestUsers() throws Exception {
    372         List<Integer> usersCreatedByTests = getUsersCreatedByTests();
    373 
    374         // The time spent on stopUser is depend on how busy the broadcast queue is.
    375         // To optimize the time to remove multiple test users, we mark all users as
    376         // stopping first, so no more broadcasts will be sent to these users, which make the queue
    377         // less busy.
    378         for (int userId : usersCreatedByTests) {
    379             stopUserAsync(userId);
    380         }
    381         for (int userId : usersCreatedByTests) {
    382             removeUser(userId);
    383         }
    384     }
    385 
    386     /**
    387      * Returns the users that have been created since running this class' setUp() method.
    388      */
    389     protected List<Integer> getUsersCreatedByTests() throws Exception {
    390         List<Integer> result = listUsers();
    391         result.removeAll(mFixedUsers);
    392         return result;
    393     }
    394 
    395     /** Removes any packages that were installed during the test. */
    396     protected void removeTestPackages() throws Exception {
    397         for (String packageName : getDevice().getUninstallablePackageNames()) {
    398             if (mFixedPackages.contains(packageName)) {
    399                 continue;
    400             }
    401             CLog.w("removing leftover package: " + packageName);
    402             getDevice().uninstallPackage(packageName);
    403         }
    404     }
    405 
    406     protected void runDeviceTestsAsUser(
    407             String pkgName, @Nullable String testClassName, int userId)
    408             throws DeviceNotAvailableException {
    409         runDeviceTestsAsUser(pkgName, testClassName, null /*testMethodName*/, userId);
    410     }
    411 
    412     protected void runDeviceTestsAsUser(
    413             String pkgName, @Nullable String testClassName, String testMethodName, int userId)
    414             throws DeviceNotAvailableException {
    415         Map<String, String> params = Collections.emptyMap();
    416         runDeviceTestsAsUser(pkgName, testClassName, testMethodName, userId, params);
    417     }
    418 
    419     protected void runDeviceTests(
    420             String pkgName, @Nullable String testClassName, String testMethodName)
    421             throws DeviceNotAvailableException {
    422         runDeviceTestsAsUser(pkgName, testClassName, testMethodName, mPrimaryUserId);
    423     }
    424 
    425     protected void runDeviceTestsAsUser(
    426             String pkgName, @Nullable String testClassName,
    427             @Nullable String testMethodName, int userId,
    428             Map<String, String> params) throws DeviceNotAvailableException {
    429         if (testClassName != null && testClassName.startsWith(".")) {
    430             testClassName = pkgName + testClassName;
    431         }
    432 
    433         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
    434                 pkgName, RUNNER, getDevice().getIDevice());
    435         testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    436         testRunner.addInstrumentationArg(
    437                 TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS));
    438         if (testClassName != null && testMethodName != null) {
    439             testRunner.setMethodName(testClassName, testMethodName);
    440         } else if (testClassName != null) {
    441             testRunner.setClassName(testClassName);
    442         }
    443 
    444         for (Map.Entry<String, String> param : params.entrySet()) {
    445             testRunner.addInstrumentationArg(param.getKey(), param.getValue());
    446         }
    447 
    448         CollectingTestListener listener = new CollectingTestListener();
    449         getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener);
    450 
    451         final TestRunResult result = listener.getCurrentRunResults();
    452         if (result.isRunFailure()) {
    453             throw new AssertionError("Failed to successfully run device tests for "
    454                     + result.getName() + ": " + result.getRunFailureMessage());
    455         }
    456         if (result.getNumTests() == 0) {
    457             throw new AssertionError("No tests were run on the device");
    458         }
    459 
    460         if (result.hasFailedTests()) {
    461             // build a meaningful error message
    462             StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
    463             for (Map.Entry<TestDescription, TestResult> resultEntry :
    464                     result.getTestResults().entrySet()) {
    465                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
    466                     errorBuilder.append(resultEntry.getKey().toString());
    467                     errorBuilder.append(":\n");
    468                     errorBuilder.append(resultEntry.getValue().getStackTrace());
    469                 }
    470             }
    471             throw new AssertionError(errorBuilder.toString());
    472         }
    473     }
    474 
    475     /** Reboots the device and block until the boot complete flag is set. */
    476     protected void rebootAndWaitUntilReady() throws DeviceNotAvailableException {
    477         getDevice().rebootUntilOnline();
    478         assertTrue("Device failed to boot", getDevice().waitForBootComplete(120000));
    479     }
    480 
    481     /** Returns true if the system supports the split between system and primary user. */
    482     protected boolean hasUserSplit() throws DeviceNotAvailableException {
    483         return getBooleanSystemProperty("ro.fw.system_user_split", false);
    484     }
    485 
    486     /** Returns a boolean value of the system property with the specified key. */
    487     protected boolean getBooleanSystemProperty(String key, boolean defaultValue)
    488             throws DeviceNotAvailableException {
    489         final String[] positiveValues = {"1", "y", "yes", "true", "on"};
    490         final String[] negativeValues = {"0", "n", "no", "false", "off"};
    491         String propertyValue = getDevice().getProperty(key);
    492         if (propertyValue == null || propertyValue.isEmpty()) {
    493             return defaultValue;
    494         }
    495         if (Arrays.asList(positiveValues).contains(propertyValue)) {
    496             return true;
    497         }
    498         if (Arrays.asList(negativeValues).contains(propertyValue)) {
    499             return false;
    500         }
    501         fail("Unexpected value of boolean system property '" + key + "': " + propertyValue);
    502         return false;
    503     }
    504 
    505     /** Checks whether it is possible to create the desired number of users. */
    506     protected boolean canCreateAdditionalUsers(int numberOfUsers)
    507             throws DeviceNotAvailableException {
    508         return listUsers().size() + numberOfUsers <= getMaxNumberOfUsersSupported();
    509     }
    510 
    511     /** Checks whether it is possible to start the desired number of users. */
    512     protected boolean canStartAdditionalUsers(int numberOfUsers)
    513             throws DeviceNotAvailableException {
    514         return listRunningUsers().size() + numberOfUsers <= getMaxNumberOfRunningUsersSupported();
    515     }
    516 
    517     protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
    518         if (mAvailableFeatures == null) {
    519             // TODO: Move this logic to ITestDevice.
    520             String command = "pm list features";
    521             String commandOutput = getDevice().executeShellCommand(command);
    522             CLog.i("Output for command " + command + ": " + commandOutput);
    523 
    524             // Extract the id of the new user.
    525             mAvailableFeatures = new HashSet<>();
    526             for (String feature: commandOutput.split("\\s+")) {
    527                 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
    528                 String[] tokens = feature.split(":");
    529                 assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
    530                         tokens.length > 1);
    531                 assertEquals(feature, "feature", tokens[0]);
    532                 mAvailableFeatures.add(tokens[1]);
    533             }
    534         }
    535         boolean result = mAvailableFeatures.contains(requiredFeature);
    536         if (!result) {
    537             CLog.d("Device doesn't have required feature "
    538             + requiredFeature + ". Test won't run.");
    539         }
    540         return result;
    541     }
    542 
    543     protected int createUser() throws Exception {
    544         int userId = createUser(0);
    545         // TODO remove this and audit tests so they start users as necessary
    546         startUser(userId);
    547         return userId;
    548     }
    549 
    550     protected int createUser(int flags) throws Exception {
    551         boolean guest = FLAG_GUEST == (flags & FLAG_GUEST);
    552         boolean ephemeral = FLAG_EPHEMERAL == (flags & FLAG_EPHEMERAL);
    553         // TODO Use ITestDevice.createUser() when guest and ephemeral is available
    554         String command ="pm create-user " + (guest ? "--guest " : "")
    555                 + (ephemeral ? "--ephemeral " : "") + "TestUser_" + System.currentTimeMillis();
    556         CLog.d("Starting command " + command);
    557         String commandOutput = getDevice().executeShellCommand(command);
    558         CLog.d("Output for command " + command + ": " + commandOutput);
    559 
    560         // Extract the id of the new user.
    561         String[] tokens = commandOutput.split("\\s+");
    562         assertTrue(tokens.length > 0);
    563         assertEquals("Success:", tokens[0]);
    564         return Integer.parseInt(tokens[tokens.length-1]);
    565     }
    566 
    567     protected int createManagedProfile(int parentUserId) throws DeviceNotAvailableException {
    568         String commandOutput = getCreateManagedProfileCommandOutput(parentUserId);
    569         return getUserIdFromCreateUserCommandOutput(commandOutput);
    570     }
    571 
    572     protected void assertCannotCreateManagedProfile(int parentUserId)
    573             throws Exception {
    574         String commandOutput = getCreateManagedProfileCommandOutput(parentUserId);
    575         if (commandOutput.startsWith("Error")) {
    576             return;
    577         }
    578         int userId = getUserIdFromCreateUserCommandOutput(commandOutput);
    579         removeUser(userId);
    580         fail("Expected not to be able to create a managed profile. Output was: " + commandOutput);
    581     }
    582 
    583     private int getUserIdFromCreateUserCommandOutput(String commandOutput) {
    584         // Extract the id of the new user.
    585         String[] tokens = commandOutput.split("\\s+");
    586         assertTrue(commandOutput + " expected to have format \"Success: {USER_ID}\"",
    587                 tokens.length > 0);
    588         assertEquals(commandOutput, "Success:", tokens[0]);
    589         return Integer.parseInt(tokens[tokens.length-1]);
    590     }
    591 
    592     private String getCreateManagedProfileCommandOutput(int parentUserId)
    593             throws DeviceNotAvailableException {
    594         String command = "pm create-user --profileOf " + parentUserId + " --managed "
    595                 + "TestProfile_" + System.currentTimeMillis();
    596         CLog.d("Starting command " + command);
    597         String commandOutput = getDevice().executeShellCommand(command);
    598         CLog.d("Output for command " + command + ": " + commandOutput);
    599         return commandOutput;
    600     }
    601 
    602     protected int getPrimaryUser() throws DeviceNotAvailableException {
    603         return getDevice().getPrimaryUserId();
    604     }
    605 
    606     protected int getUserSerialNumber(int userId) throws DeviceNotAvailableException{
    607         // TODO: Move this logic to ITestDevice.
    608         // dumpsys user return lines like "UserInfo{0:Owner:13} serialNo=0"
    609         String commandOutput = getDevice().executeShellCommand("dumpsys user");
    610         String[] tokens = commandOutput.split("\\n");
    611         for (String token : tokens) {
    612             token = token.trim();
    613             if (token.contains("UserInfo{" + userId + ":")) {
    614                 String[] split = token.split("serialNo=");
    615                 assertTrue(split.length == 2);
    616                 int serialNumber = Integer.parseInt(split[1]);
    617                 CLog.d("Serial number of user " + userId + ": "
    618                         + serialNumber);
    619                 return serialNumber;
    620             }
    621         }
    622         fail("Couldn't find user " + userId);
    623         return -1;
    624     }
    625 
    626     protected boolean setProfileOwner(String componentName, int userId, boolean expectFailure)
    627             throws DeviceNotAvailableException {
    628         String command = "dpm set-profile-owner --user " + userId + " '" + componentName + "'";
    629         String commandOutput = getDevice().executeShellCommand(command);
    630         boolean success = commandOutput.startsWith("Success:");
    631         // If we succeeded always log, if we are expecting failure don't log failures
    632         // as call stacks for passing tests confuse the logs.
    633         if (success || !expectFailure) {
    634             CLog.d("Output for command " + command + ": " + commandOutput);
    635         } else {
    636             CLog.d("Command Failed " + command);
    637         }
    638         return success;
    639     }
    640 
    641     protected void setProfileOwnerOrFail(String componentName, int userId)
    642             throws Exception {
    643         if (!setProfileOwner(componentName, userId, /*expectFailure*/ false)) {
    644             if (userId != 0) { // don't remove system user.
    645                 removeUser(userId);
    646             }
    647             fail("Failed to set profile owner");
    648         }
    649     }
    650 
    651     protected void setProfileOwnerExpectingFailure(String componentName, int userId)
    652             throws Exception {
    653         if (setProfileOwner(componentName, userId, /* expectFailure =*/ true)) {
    654             if (userId != 0) { // don't remove system user.
    655                 removeUser(userId);
    656             }
    657             fail("Setting profile owner should have failed.");
    658         }
    659     }
    660 
    661     private String setDeviceAdminInner(String componentName, int userId)
    662             throws DeviceNotAvailableException {
    663         String command = "dpm set-active-admin --user " + userId + " '" + componentName + "'";
    664         String commandOutput = getDevice().executeShellCommand(command);
    665         return commandOutput;
    666     }
    667 
    668     protected void setDeviceAdmin(String componentName, int userId)
    669             throws DeviceNotAvailableException {
    670         String commandOutput = setDeviceAdminInner(componentName, userId);
    671         CLog.d("Output for command " + commandOutput
    672                 + ": " + commandOutput);
    673         assertTrue(commandOutput + " expected to start with \"Success:\"",
    674                 commandOutput.startsWith("Success:"));
    675     }
    676 
    677     protected void setDeviceAdminExpectingFailure(String componentName, int userId,
    678             String errorMessage) throws DeviceNotAvailableException {
    679         String commandOutput = setDeviceAdminInner(componentName, userId);
    680         if (!commandOutput.contains(errorMessage)) {
    681             fail(commandOutput + " expected to contain \"" + errorMessage + "\"");
    682         }
    683     }
    684 
    685     protected boolean setDeviceOwner(String componentName, int userId, boolean expectFailure)
    686             throws DeviceNotAvailableException {
    687         String command = "dpm set-device-owner --user " + userId + " '" + componentName + "'";
    688         String commandOutput = getDevice().executeShellCommand(command);
    689         boolean success = commandOutput.startsWith("Success:");
    690         // If we succeeded always log, if we are expecting failure don't log failures
    691         // as call stacks for passing tests confuse the logs.
    692         if (success || !expectFailure) {
    693             CLog.d("Output for command " + command + ": " + commandOutput);
    694         } else {
    695             CLog.d("Command Failed " + command);
    696         }
    697         return success;
    698     }
    699 
    700     protected void setDeviceOwnerOrFail(String componentName, int userId)
    701             throws Exception {
    702         assertTrue(setDeviceOwner(componentName, userId, /* expectFailure =*/ false));
    703     }
    704 
    705     protected void setDeviceOwnerExpectingFailure(String componentName, int userId)
    706             throws Exception {
    707         assertFalse(setDeviceOwner(componentName, userId, /* expectFailure =*/ true));
    708     }
    709 
    710     protected String getSettings(String namespace, String name, int userId)
    711             throws DeviceNotAvailableException {
    712         String command = "settings --user " + userId + " get " + namespace + " " + name;
    713         String commandOutput = getDevice().executeShellCommand(command);
    714         CLog.d("Output for command " + command + ": " + commandOutput);
    715         return commandOutput.replace("\n", "").replace("\r", "");
    716     }
    717 
    718     protected void putSettings(String namespace, String name, String value, int userId)
    719             throws DeviceNotAvailableException {
    720         String command = "settings --user " + userId + " put " + namespace + " " + name
    721                 + " " + value;
    722         String commandOutput = getDevice().executeShellCommand(command);
    723         CLog.d("Output for command " + command + ": " + commandOutput);
    724     }
    725 
    726     protected boolean removeAdmin(String componentName, int userId)
    727             throws DeviceNotAvailableException {
    728         String command = "dpm remove-active-admin --user " + userId + " '" + componentName + "'";
    729         String commandOutput = getDevice().executeShellCommand(command);
    730         CLog.d("Output for command " + command + ": " + commandOutput);
    731         return commandOutput.startsWith("Success:");
    732     }
    733 
    734     // Tries to remove and profile or device owners it finds.
    735     protected void removeOwners() throws DeviceNotAvailableException {
    736         String command = "dumpsys device_policy";
    737         String commandOutput = getDevice().executeShellCommand(command);
    738         String[] lines = commandOutput.split("\\r?\\n");
    739         for (int i = 0; i < lines.length; ++i) {
    740             String line = lines[i].trim();
    741             if (line.contains("Profile Owner")) {
    742                 // Line is "Profile owner (User <id>):
    743                 String[] tokens = line.split("\\(|\\)| ");
    744                 int userId = Integer.parseInt(tokens[4]);
    745                 i++;
    746                 line = lines[i].trim();
    747                 // Line is admin=ComponentInfo{<component>}
    748                 tokens = line.split("\\{|\\}");
    749                 String componentName = tokens[1];
    750                 CLog.w("Cleaning up profile owner " + userId + " " + componentName);
    751                 removeAdmin(componentName, userId);
    752             } else if (line.contains("Device Owner:")) {
    753                 i++;
    754                 line = lines[i].trim();
    755                 // Line is admin=ComponentInfo{<component>}
    756                 String[] tokens = line.split("\\{|\\}");
    757                 String componentName = tokens[1];
    758                 // Skip to user id line.
    759                 i += 4;
    760                 line = lines[i].trim();
    761                 // Line is User ID: <N>
    762                 tokens = line.split(":");
    763                 int userId = Integer.parseInt(tokens[1].trim());
    764                 CLog.w("Cleaning up device owner " + userId + " " + componentName);
    765                 removeAdmin(componentName, userId);
    766             }
    767         }
    768     }
    769 
    770     /**
    771      * Runs pm enable command to enable a package or component. Returns the command result.
    772      */
    773     protected String enableComponentOrPackage(int userId, String packageOrComponent)
    774             throws DeviceNotAvailableException {
    775         String command = "pm enable --user " + userId + " " + packageOrComponent;
    776         String result = getDevice().executeShellCommand(command);
    777         CLog.d("Output for command " + command + ": " + result);
    778         return result;
    779     }
    780 
    781     /**
    782      * Runs pm disable command to disable a package or component. Returns the command result.
    783      */
    784     protected String disableComponentOrPackage(int userId, String packageOrComponent)
    785             throws DeviceNotAvailableException {
    786         String command = "pm disable --user " + userId + " " + packageOrComponent;
    787         String result = getDevice().executeShellCommand(command);
    788         CLog.d("Output for command " + command + ": " + result);
    789         return result;
    790     }
    791 
    792     protected interface SuccessCondition {
    793         boolean check() throws Exception;
    794     }
    795 
    796     protected void assertUserGetsRemoved(int userId) throws Exception {
    797         tryWaitForSuccess(() -> !listUsers().contains(userId),
    798                 "The user " + userId + " has not been removed",
    799                 TIMEOUT_USER_REMOVED_MILLIS
    800                 );
    801     }
    802 
    803     protected void tryWaitForSuccess(SuccessCondition successCondition, String failureMessage,
    804             long timeoutMillis) throws Exception {
    805         long epoch = System.currentTimeMillis();
    806         while (System.currentTimeMillis() - epoch <= timeoutMillis) {
    807             Thread.sleep(WAIT_SAMPLE_INTERVAL_MILLIS);
    808             if (successCondition.check()) {
    809                 return;
    810             }
    811         }
    812         fail(failureMessage);
    813     }
    814 
    815     /**
    816      * Sets a user restriction via SetPolicyActivity.
    817      * <p>IMPORTANT: The package that contains SetPolicyActivity must have been installed prior to
    818      * calling this method.
    819      * @param key user restriction key
    820      * @param value true if we should set the restriction, false if we should clear it
    821      * @param userId userId to set/clear the user restriction on
    822      * @param packageName package where SetPolicyActivity is installed
    823      * @return The output of the command
    824      * @throws DeviceNotAvailableException
    825      */
    826     protected String changeUserRestriction(String key, boolean value, int userId,
    827             String packageName) throws DeviceNotAvailableException {
    828         return changePolicy(getUserRestrictionCommand(value),
    829                 " --es extra-restriction-key " + key, userId, packageName);
    830     }
    831 
    832     /**
    833      * Same as {@link #changeUserRestriction(String, boolean, int, String)} but asserts that it
    834      * succeeds.
    835      */
    836     protected void changeUserRestrictionOrFail(String key, boolean value, int userId,
    837             String packageName) throws DeviceNotAvailableException {
    838         changePolicyOrFail(getUserRestrictionCommand(value), " --es extra-restriction-key " + key,
    839                 userId, packageName);
    840     }
    841 
    842     /**
    843      * Sets some policy via SetPolicyActivity.
    844      * <p>IMPORTANT: The package that contains SetPolicyActivity must have been installed prior to
    845      * calling this method.
    846      * @param command command to pass to SetPolicyActivity
    847      * @param extras extras to pass to SetPolicyActivity
    848      * @param userId the userId where we invoke SetPolicyActivity
    849      * @param packageName where SetPolicyActivity is installed
    850      * @return The output of the command
    851      * @throws DeviceNotAvailableException
    852      */
    853     protected String changePolicy(String command, String extras, int userId, String packageName)
    854             throws DeviceNotAvailableException {
    855         String adbCommand = "am start -W --user " + userId
    856                 + " -c android.intent.category.DEFAULT "
    857                 + " --es extra-command " + command
    858                 + " " + extras
    859                 + " " + packageName + "/.SetPolicyActivity";
    860         String commandOutput = getDevice().executeShellCommand(adbCommand);
    861         CLog.d("Output for command " + adbCommand + ": " + commandOutput);
    862         return commandOutput;
    863     }
    864 
    865     /**
    866      * Same as {@link #changePolicy(String, String, int, String)} but asserts that it succeeds.
    867      */
    868     protected void changePolicyOrFail(String command, String extras, int userId,
    869             String packageName) throws DeviceNotAvailableException {
    870         String commandOutput = changePolicy(command, extras, userId, packageName);
    871         assertTrue("Command was expected to succeed " + commandOutput,
    872                 commandOutput.contains("Status: ok"));
    873     }
    874 
    875     private String getUserRestrictionCommand(boolean setRestriction) {
    876         if (setRestriction) {
    877             return "add-restriction";
    878         }
    879         return "clear-restriction";
    880     }
    881 
    882     /**
    883      * Set lockscreen password / work challenge for the given user, null or "" means clear
    884      */
    885     protected void changeUserCredential(String newCredential, String oldCredential, int userId)
    886             throws DeviceNotAvailableException {
    887         final String oldCredentialArgument = (oldCredential == null || oldCredential.isEmpty()) ? ""
    888                 : ("--old " + oldCredential);
    889         if (newCredential != null && !newCredential.isEmpty()) {
    890             String commandOutput = getDevice().executeShellCommand(String.format(
    891                     "cmd lock_settings set-password --user %d %s %s", userId, oldCredentialArgument,
    892                     newCredential));
    893             if (!commandOutput.startsWith("Password set to")) {
    894                 fail("Failed to set user credential: " + commandOutput);
    895             }
    896         } else {
    897             String commandOutput = getDevice().executeShellCommand(String.format(
    898                     "cmd lock_settings clear --user %d %s", userId, oldCredentialArgument));
    899             if (!commandOutput.startsWith("Lock credential cleared")) {
    900                 fail("Failed to clear user credential: " + commandOutput);
    901             }
    902         }
    903     }
    904 
    905     /**
    906      * Verifies the lock credential for the given user.
    907      *
    908      * @param credential The credential to verify.
    909      * @param userId The id of the user.
    910      */
    911     protected void verifyUserCredential(String credential, int userId)
    912             throws DeviceNotAvailableException {
    913         String commandOutput = verifyUserCredentialCommandOutput(credential, userId);
    914         if (!commandOutput.startsWith(VERIFY_CREDENTIAL_CONFIRMATION)) {
    915             fail("Failed to verify user credential: " + commandOutput);
    916         }
    917      }
    918 
    919     /**
    920      * Verifies the lock credential for the given user, which unlocks the user, and returns
    921      * whether it was successful or not.
    922      *
    923      * @param credential The credential to verify.
    924      * @param userId The id of the user.
    925      */
    926     protected boolean verifyUserCredentialIsCorrect(String credential, int userId)
    927             throws DeviceNotAvailableException {
    928         String commandOutput = verifyUserCredentialCommandOutput(credential, userId);
    929         return commandOutput.startsWith(VERIFY_CREDENTIAL_CONFIRMATION);
    930     }
    931 
    932     /**
    933      * Verifies the lock credential for the given user, which unlocks the user. Returns the
    934      * commandline output, which includes whether the verification was successful.
    935      *
    936      * @param credential The credential to verify.
    937      * @param userId The id of the user.
    938      * @return The command line output.
    939      */
    940     protected String verifyUserCredentialCommandOutput(String credential, int userId)
    941             throws DeviceNotAvailableException {
    942         final String credentialArgument = (credential == null || credential.isEmpty())
    943                 ? "" : ("--old " + credential);
    944         String commandOutput = getDevice().executeShellCommand(String.format(
    945                 "cmd lock_settings verify --user %d %s", userId, credentialArgument));
    946         return commandOutput;
    947     }
    948 
    949     protected void wakeupAndDismissKeyguard() throws Exception {
    950         executeShellCommand("input keyevent KEYCODE_WAKEUP");
    951         executeShellCommand("wm dismiss-keyguard");
    952     }
    953 
    954     protected void pressPowerButton() throws Exception {
    955         executeShellCommand("input keyevent KEYCODE_POWER");
    956     }
    957 
    958     private void stayAwake() throws Exception {
    959         executeShellCommand(
    960                 "settings put global stay_on_while_plugged_in " + STAY_ON_WHILE_PLUGGED_IN_FLAGS);
    961     }
    962 
    963     protected void startActivityAsUser(int userId, String packageName, String activityName)
    964         throws Exception {
    965         wakeupAndDismissKeyguard();
    966         String command = "am start -W --user " + userId + " " + packageName + "/" + activityName;
    967         getDevice().executeShellCommand(command);
    968     }
    969 
    970     protected String getDefaultLauncher() throws Exception {
    971         final String PREFIX = "Launcher: ComponentInfo{";
    972         final String POSTFIX = "}";
    973         final String commandOutput =
    974                 getDevice().executeShellCommand("cmd shortcut get-default-launcher");
    975         if (commandOutput == null) {
    976             return null;
    977         }
    978         String[] lines = commandOutput.split("\\r?\\n");
    979         for (String line : lines) {
    980             if (line.startsWith(PREFIX) && line.endsWith(POSTFIX)) {
    981                 return line.substring(PREFIX.length(), line.length() - POSTFIX.length());
    982             }
    983         }
    984         throw new Exception("Default launcher not found");
    985     }
    986 }
    987