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