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.TestIdentifier;
     22 import com.android.ddmlib.testrunner.TestResult;
     23 import com.android.ddmlib.testrunner.TestResult.TestStatus;
     24 import com.android.ddmlib.testrunner.TestRunResult;
     25 import com.android.tradefed.build.IBuildInfo;
     26 import com.android.tradefed.device.DeviceNotAvailableException;
     27 import com.android.tradefed.log.LogUtil.CLog;
     28 import com.android.tradefed.result.CollectingTestListener;
     29 import com.android.tradefed.testtype.DeviceTestCase;
     30 import com.android.tradefed.testtype.IBuildReceiver;
     31 
     32 import java.io.FileNotFoundException;
     33 import java.util.ArrayList;
     34 import java.util.Arrays;
     35 import java.util.Collections;
     36 import java.util.HashSet;
     37 import java.util.List;
     38 import java.util.Map;
     39 import java.util.Set;
     40 import java.util.concurrent.TimeUnit;
     41 
     42 import javax.annotation.Nullable;
     43 
     44 /**
     45  * Base class for device policy tests. It offers utility methods to run tests, set device or profile
     46  * owner, etc.
     47  */
     48 public class BaseDevicePolicyTest extends DeviceTestCase implements IBuildReceiver {
     49 
     50     private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
     51 
     52     protected static final int USER_SYSTEM = 0; // From the UserHandle class.
     53 
     54     protected static final int USER_OWNER = 0;
     55 
     56     private static final long TIMEOUT_USER_REMOVED_MILLIS = TimeUnit.SECONDS.toMillis(15);
     57     private static final long WAIT_SAMPLE_INTERVAL_MILLIS = 200;
     58 
     59     /**
     60      * The defined timeout (in milliseconds) is used as a maximum waiting time when expecting the
     61      * command output from the device. At any time, if the shell command does not output anything
     62      * for a period longer than defined timeout the Tradefed run terminates.
     63      */
     64     private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(20);
     65 
     66     /** instrumentation test runner argument key used for individual test timeout */
     67     protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
     68 
     69     /**
     70      * Sets timeout (in milliseconds) that will be applied to each test. In the
     71      * event of a test timeout it will log the results and proceed with executing the next test.
     72      */
     73     private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
     74 
     75     // From the UserInfo class
     76     protected static final int FLAG_PRIMARY = 0x00000001;
     77     protected static final int FLAG_GUEST = 0x00000004;
     78     protected static final int FLAG_EPHEMERAL = 0x00000100;
     79     protected static final int FLAG_MANAGED_PROFILE = 0x00000020;
     80 
     81     protected static interface Settings {
     82         public static final String GLOBAL_NAMESPACE = "global";
     83         public static interface Global {
     84             public static final String DEVICE_PROVISIONED = "device_provisioned";
     85         }
     86     }
     87 
     88     protected IBuildInfo mCtsBuild;
     89 
     90     private String mPackageVerifier;
     91     private HashSet<String> mAvailableFeatures;
     92 
     93     /** Packages installed as part of the tests */
     94     private Set<String> mFixedPackages;
     95 
     96     /** Whether DPM is supported. */
     97     protected boolean mHasFeature;
     98     protected int mPrimaryUserId;
     99 
    100     /** Whether multi-user is supported. */
    101     protected boolean mSupportsMultiUser;
    102 
    103     /** Whether file-based encryption (FBE) is supported. */
    104     protected boolean mSupportsFbe;
    105 
    106     /** Users we shouldn't delete in the tests */
    107     private ArrayList<Integer> mFixedUsers;
    108 
    109     @Override
    110     public void setBuild(IBuildInfo buildInfo) {
    111         mCtsBuild = buildInfo;
    112     }
    113 
    114     @Override
    115     protected void setUp() throws Exception {
    116         super.setUp();
    117         assertNotNull(mCtsBuild);  // ensure build has been set before test is run.
    118         mHasFeature = getDevice().getApiLevel() >= 21 /* Build.VERSION_CODES.L */
    119                 && hasDeviceFeature("android.software.device_admin");
    120         mSupportsMultiUser = getMaxNumberOfUsersSupported() > 1;
    121         mSupportsFbe = hasDeviceFeature("android.software.file_based_encryption");
    122         mFixedPackages = getDevice().getInstalledPackageNames();
    123 
    124         // disable the package verifier to avoid the dialog when installing an app
    125         mPackageVerifier = getDevice().executeShellCommand(
    126                 "settings get global package_verifier_enable");
    127         getDevice().executeShellCommand("settings put global package_verifier_enable 0");
    128 
    129         mFixedUsers = new ArrayList();
    130         mPrimaryUserId = getPrimaryUser();
    131         mFixedUsers.add(mPrimaryUserId);
    132         if (mPrimaryUserId != USER_SYSTEM) {
    133             mFixedUsers.add(USER_SYSTEM);
    134         }
    135         switchUser(mPrimaryUserId);
    136         removeOwners();
    137         removeTestUsers();
    138         // Unlock keyguard before test
    139         wakeupAndDismissKeyguard();
    140     }
    141 
    142     @Override
    143     protected void tearDown() throws Exception {
    144         // reset the package verifier setting to its original value
    145         getDevice().executeShellCommand("settings put global package_verifier_enable "
    146                 + mPackageVerifier);
    147         removeOwners();
    148         removeTestUsers();
    149         removeTestPackages();
    150         super.tearDown();
    151     }
    152 
    153     protected void installAppAsUser(String appFileName, int userId) throws FileNotFoundException,
    154             DeviceNotAvailableException {
    155         installAppAsUser(appFileName, true, userId);
    156     }
    157 
    158     protected void installAppAsUser(String appFileName, boolean grantPermissions, int userId)
    159             throws FileNotFoundException, DeviceNotAvailableException {
    160         CLog.d("Installing app " + appFileName + " for user " + userId);
    161         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
    162         String result = getDevice().installPackageForUser(
    163                 buildHelper.getTestFile(appFileName), true, grantPermissions, userId, "-t");
    164         assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result,
    165                 result);
    166     }
    167 
    168     protected void forceStopPackageForUser(String packageName, int userId) throws Exception {
    169         // TODO Move this logic to ITestDevice
    170         executeShellCommand("am force-stop --user " + userId + " " + packageName);
    171     }
    172 
    173     protected void executeShellCommand(final String command) throws Exception {
    174         CLog.d("Starting command " + command);
    175         String commandOutput = getDevice().executeShellCommand(command);
    176         CLog.d("Output for command " + command + ": " + commandOutput);
    177     }
    178 
    179     /** Initializes the user with the given id. This is required so that apps can run on it. */
    180     protected void startUser(int userId) throws Exception {
    181         getDevice().startUser(userId);
    182     }
    183 
    184     protected void switchUser(int userId) throws Exception {
    185         // TODO Move this logic to ITestDevice
    186         executeShellCommand("am switch-user " + userId);
    187     }
    188 
    189     protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
    190         return getDevice().getMaxNumberOfUsersSupported();
    191     }
    192 
    193     protected int getUserFlags(int userId) throws DeviceNotAvailableException {
    194         String command = "pm list users";
    195         String commandOutput = getDevice().executeShellCommand(command);
    196         CLog.i("Output for command " + command + ": " + commandOutput);
    197 
    198         String[] lines = commandOutput.split("\\r?\\n");
    199         assertTrue(commandOutput + " should contain at least one line", lines.length >= 1);
    200         for (int i = 1; i < lines.length; i++) {
    201             // Individual user is printed out like this:
    202             // \tUserInfo{$id$:$name$:$Integer.toHexString(flags)$} [running]
    203             String[] tokens = lines[i].split("\\{|\\}|:");
    204             assertTrue(lines[i] + " doesn't contain 4 or 5 tokens",
    205                     tokens.length == 4 || tokens.length == 5);
    206             // If the user IDs match, return the flags.
    207             if (Integer.parseInt(tokens[1]) == userId) {
    208                 return Integer.parseInt(tokens[3], 16);
    209             }
    210         }
    211         fail("User not found");
    212         return 0;
    213     }
    214 
    215     protected ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
    216         return getDevice().listUsers();
    217     }
    218 
    219     protected int getFirstManagedProfileUserId() throws DeviceNotAvailableException {
    220         for (int userId : listUsers()) {
    221             if ((getUserFlags(userId) & FLAG_MANAGED_PROFILE) != 0) {
    222                 return userId;
    223             }
    224         }
    225         fail("Managed profile not found");
    226         return 0;
    227     }
    228 
    229     protected void stopUser(int userId) throws Exception {
    230         // Wait for the broadcast queue to be idle first to workaround the stop-user timeout issue.
    231         waitForBroadcastIdle();
    232         String stopUserCommand = "am stop-user -w -f " + userId;
    233         CLog.d("starting command \"" + stopUserCommand + "\" and waiting.");
    234         CLog.d("Output for command " + stopUserCommand + ": "
    235                 + getDevice().executeShellCommand(stopUserCommand));
    236     }
    237 
    238     private void waitForBroadcastIdle() throws Exception {
    239         getDevice().executeShellCommand("am wait-for-broadcast-idle");
    240     }
    241 
    242     protected void removeUser(int userId) throws Exception  {
    243         if (listUsers().contains(userId) && userId != USER_SYSTEM) {
    244             // Don't log output, as tests sometimes set no debug user restriction, which
    245             // causes this to fail, we should still continue and remove the user.
    246             String stopUserCommand = "am stop-user -w -f " + userId;
    247             CLog.d("stopping and removing user " + userId);
    248             getDevice().executeShellCommand(stopUserCommand);
    249             assertTrue("Couldn't remove user", getDevice().removeUser(userId));
    250         }
    251     }
    252 
    253     protected void removeTestUsers() throws Exception {
    254         for (int userId : getUsersCreatedByTests()) {
    255             removeUser(userId);
    256         }
    257     }
    258 
    259     /**
    260      * Returns the users that have been created since running this class' setUp() method.
    261      */
    262     protected List<Integer> getUsersCreatedByTests() throws Exception {
    263         List<Integer> result = listUsers();
    264         result.removeAll(mFixedUsers);
    265         return result;
    266     }
    267 
    268     /** Removes any packages that were installed during the test. */
    269     protected void removeTestPackages() throws Exception {
    270         for (String packageName : getDevice().getUninstallablePackageNames()) {
    271             if (mFixedPackages.contains(packageName)) {
    272                 continue;
    273             }
    274             CLog.w("removing leftover package: " + packageName);
    275             getDevice().uninstallPackage(packageName);
    276         }
    277     }
    278 
    279     protected void runDeviceTestsAsUser(
    280             String pkgName, @Nullable String testClassName, int userId)
    281             throws DeviceNotAvailableException {
    282         runDeviceTestsAsUser(pkgName, testClassName, null /*testMethodName*/, userId);
    283     }
    284 
    285     protected void runDeviceTestsAsUser(
    286             String pkgName, @Nullable String testClassName, String testMethodName, int userId)
    287             throws DeviceNotAvailableException {
    288         Map<String, String> params = Collections.emptyMap();
    289         runDeviceTestsAsUser(pkgName, testClassName, testMethodName, userId, params);
    290     }
    291 
    292     protected void runDeviceTests(
    293             String pkgName, @Nullable String testClassName, String testMethodName)
    294             throws DeviceNotAvailableException {
    295         runDeviceTestsAsUser(pkgName, testClassName, testMethodName, mPrimaryUserId);
    296     }
    297 
    298     protected void runDeviceTestsAsUser(
    299             String pkgName, @Nullable String testClassName,
    300             @Nullable String testMethodName, int userId,
    301             Map<String, String> params) throws DeviceNotAvailableException {
    302         if (testClassName != null && testClassName.startsWith(".")) {
    303             testClassName = pkgName + testClassName;
    304         }
    305 
    306         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
    307                 pkgName, RUNNER, getDevice().getIDevice());
    308         testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    309         testRunner.addInstrumentationArg(
    310                 TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS));
    311         if (testClassName != null && testMethodName != null) {
    312             testRunner.setMethodName(testClassName, testMethodName);
    313         } else if (testClassName != null) {
    314             testRunner.setClassName(testClassName);
    315         }
    316 
    317         for (Map.Entry<String, String> param : params.entrySet()) {
    318             testRunner.addInstrumentationArg(param.getKey(), param.getValue());
    319         }
    320 
    321         CollectingTestListener listener = new CollectingTestListener();
    322         assertTrue(getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener));
    323 
    324         final TestRunResult result = listener.getCurrentRunResults();
    325         if (result.isRunFailure()) {
    326             throw new AssertionError("Failed to successfully run device tests for "
    327                     + result.getName() + ": " + result.getRunFailureMessage());
    328         }
    329         if (result.getNumTests() == 0) {
    330             throw new AssertionError("No tests were run on the device");
    331         }
    332 
    333         if (result.hasFailedTests()) {
    334             // build a meaningful error message
    335             StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
    336             for (Map.Entry<TestIdentifier, TestResult> resultEntry :
    337                     result.getTestResults().entrySet()) {
    338                 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
    339                     errorBuilder.append(resultEntry.getKey().toString());
    340                     errorBuilder.append(":\n");
    341                     errorBuilder.append(resultEntry.getValue().getStackTrace());
    342                 }
    343             }
    344             throw new AssertionError(errorBuilder.toString());
    345         }
    346     }
    347 
    348     /** Reboots the device and block until the boot complete flag is set. */
    349     protected void rebootAndWaitUntilReady() throws DeviceNotAvailableException {
    350         getDevice().executeShellCommand("reboot");
    351         assertTrue("Device failed to boot", getDevice().waitForBootComplete(120000));
    352     }
    353 
    354     /** Returns true if the system supports the split between system and primary user. */
    355     protected boolean hasUserSplit() throws DeviceNotAvailableException {
    356         return getBooleanSystemProperty("ro.fw.system_user_split", false);
    357     }
    358 
    359     /** Returns a boolean value of the system property with the specified key. */
    360     protected boolean getBooleanSystemProperty(String key, boolean defaultValue)
    361             throws DeviceNotAvailableException {
    362         final String[] positiveValues = {"1", "y", "yes", "true", "on"};
    363         final String[] negativeValues = {"0", "n", "no", "false", "off"};
    364         String propertyValue = getDevice().getProperty(key);
    365         if (propertyValue == null || propertyValue.isEmpty()) {
    366             return defaultValue;
    367         }
    368         if (Arrays.asList(positiveValues).contains(propertyValue)) {
    369             return true;
    370         }
    371         if (Arrays.asList(negativeValues).contains(propertyValue)) {
    372             return false;
    373         }
    374         fail("Unexpected value of boolean system property '" + key + "': " + propertyValue);
    375         return false;
    376     }
    377 
    378     /** Checks whether it is possible to create the desired number of users. */
    379     protected boolean canCreateAdditionalUsers(int numberOfUsers)
    380             throws DeviceNotAvailableException {
    381         return listUsers().size() + numberOfUsers <= getMaxNumberOfUsersSupported();
    382     }
    383 
    384     protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
    385         if (mAvailableFeatures == null) {
    386             // TODO: Move this logic to ITestDevice.
    387             String command = "pm list features";
    388             String commandOutput = getDevice().executeShellCommand(command);
    389             CLog.i("Output for command " + command + ": " + commandOutput);
    390 
    391             // Extract the id of the new user.
    392             mAvailableFeatures = new HashSet<>();
    393             for (String feature: commandOutput.split("\\s+")) {
    394                 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
    395                 String[] tokens = feature.split(":");
    396                 assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
    397                         tokens.length > 1);
    398                 assertEquals(feature, "feature", tokens[0]);
    399                 mAvailableFeatures.add(tokens[1]);
    400             }
    401         }
    402         boolean result = mAvailableFeatures.contains(requiredFeature);
    403         if (!result) {
    404             CLog.d("Device doesn't have required feature "
    405             + requiredFeature + ". Test won't run.");
    406         }
    407         return result;
    408     }
    409 
    410     protected int createUser() throws Exception {
    411         int userId = createUser(0);
    412         // TODO remove this and audit tests so they start users as necessary
    413         startUser(userId);
    414         return userId;
    415     }
    416 
    417     protected int createUser(int flags) throws Exception {
    418         boolean guest = FLAG_GUEST == (flags & FLAG_GUEST);
    419         boolean ephemeral = FLAG_EPHEMERAL == (flags & FLAG_EPHEMERAL);
    420         // TODO Use ITestDevice.createUser() when guest and ephemeral is available
    421         String command ="pm create-user " + (guest ? "--guest " : "")
    422                 + (ephemeral ? "--ephemeral " : "") + "TestUser_" + System.currentTimeMillis();
    423         CLog.d("Starting command " + command);
    424         String commandOutput = getDevice().executeShellCommand(command);
    425         CLog.d("Output for command " + command + ": " + commandOutput);
    426 
    427         // Extract the id of the new user.
    428         String[] tokens = commandOutput.split("\\s+");
    429         assertTrue(tokens.length > 0);
    430         assertEquals("Success:", tokens[0]);
    431         return Integer.parseInt(tokens[tokens.length-1]);
    432     }
    433 
    434     protected int createManagedProfile(int parentUserId) throws DeviceNotAvailableException {
    435         String commandOutput = getCreateManagedProfileCommandOutput(parentUserId);
    436         return getUserIdFromCreateUserCommandOutput(commandOutput);
    437     }
    438 
    439     protected void assertCannotCreateManagedProfile(int parentUserId)
    440             throws Exception {
    441         String commandOutput = getCreateManagedProfileCommandOutput(parentUserId);
    442         if (commandOutput.startsWith("Error")) {
    443             return;
    444         }
    445         int userId = getUserIdFromCreateUserCommandOutput(commandOutput);
    446         removeUser(userId);
    447         fail("Expected not to be able to create a managed profile. Output was: " + commandOutput);
    448     }
    449 
    450     private int getUserIdFromCreateUserCommandOutput(String commandOutput) {
    451         // Extract the id of the new user.
    452         String[] tokens = commandOutput.split("\\s+");
    453         assertTrue(commandOutput + " expected to have format \"Success: {USER_ID}\"",
    454                 tokens.length > 0);
    455         assertEquals(commandOutput, "Success:", tokens[0]);
    456         return Integer.parseInt(tokens[tokens.length-1]);
    457     }
    458 
    459     private String getCreateManagedProfileCommandOutput(int parentUserId)
    460             throws DeviceNotAvailableException {
    461         String command = "pm create-user --profileOf " + parentUserId + " --managed "
    462                 + "TestProfile_" + System.currentTimeMillis();
    463         CLog.d("Starting command " + command);
    464         String commandOutput = getDevice().executeShellCommand(command);
    465         CLog.d("Output for command " + command + ": " + commandOutput);
    466         return commandOutput;
    467     }
    468 
    469     protected int getPrimaryUser() throws DeviceNotAvailableException {
    470         return getDevice().getPrimaryUserId();
    471     }
    472 
    473     protected int getUserSerialNumber(int userId) throws DeviceNotAvailableException{
    474         // TODO: Move this logic to ITestDevice.
    475         // dumpsys user return lines like "UserInfo{0:Owner:13} serialNo=0"
    476         String commandOutput = getDevice().executeShellCommand("dumpsys user");
    477         String[] tokens = commandOutput.split("\\n");
    478         for (String token : tokens) {
    479             token = token.trim();
    480             if (token.contains("UserInfo{" + userId + ":")) {
    481                 String[] split = token.split("serialNo=");
    482                 assertTrue(split.length == 2);
    483                 int serialNumber = Integer.parseInt(split[1]);
    484                 CLog.d("Serial number of user " + userId + ": "
    485                         + serialNumber);
    486                 return serialNumber;
    487             }
    488         }
    489         fail("Couldn't find user " + userId);
    490         return -1;
    491     }
    492 
    493     protected boolean setProfileOwner(String componentName, int userId, boolean expectFailure)
    494             throws DeviceNotAvailableException {
    495         String command = "dpm set-profile-owner --user " + userId + " '" + componentName + "'";
    496         String commandOutput = getDevice().executeShellCommand(command);
    497         boolean success = commandOutput.startsWith("Success:");
    498         // If we succeeded always log, if we are expecting failure don't log failures
    499         // as call stacks for passing tests confuse the logs.
    500         if (success || !expectFailure) {
    501             CLog.d("Output for command " + command + ": " + commandOutput);
    502         } else {
    503             CLog.d("Command Failed " + command);
    504         }
    505         return success;
    506     }
    507 
    508     protected void setProfileOwnerOrFail(String componentName, int userId)
    509             throws Exception {
    510         if (!setProfileOwner(componentName, userId, /*expectFailure*/ false)) {
    511             if (userId != 0) { // don't remove system user.
    512                 removeUser(userId);
    513             }
    514             fail("Failed to set profile owner");
    515         }
    516     }
    517 
    518     protected void setProfileOwnerExpectingFailure(String componentName, int userId)
    519             throws Exception {
    520         if (setProfileOwner(componentName, userId, /* expectFailure =*/ true)) {
    521             if (userId != 0) { // don't remove system user.
    522                 removeUser(userId);
    523             }
    524             fail("Setting profile owner should have failed.");
    525         }
    526     }
    527 
    528     private String setDeviceAdminInner(String componentName, int userId)
    529             throws DeviceNotAvailableException {
    530         String command = "dpm set-active-admin --user " + userId + " '" + componentName + "'";
    531         String commandOutput = getDevice().executeShellCommand(command);
    532         return commandOutput;
    533     }
    534 
    535     protected void setDeviceAdmin(String componentName, int userId)
    536             throws DeviceNotAvailableException {
    537         String commandOutput = setDeviceAdminInner(componentName, userId);
    538         CLog.d("Output for command " + commandOutput
    539                 + ": " + commandOutput);
    540         assertTrue(commandOutput + " expected to start with \"Success:\"",
    541                 commandOutput.startsWith("Success:"));
    542     }
    543 
    544     protected void setDeviceAdminExpectingFailure(String componentName, int userId,
    545             String errorMessage) throws DeviceNotAvailableException {
    546         String commandOutput = setDeviceAdminInner(componentName, userId);
    547         if (!commandOutput.contains(errorMessage)) {
    548             fail(commandOutput + " expected to contain \"" + errorMessage + "\"");
    549         }
    550     }
    551 
    552     protected boolean setDeviceOwner(String componentName, int userId, boolean expectFailure)
    553             throws DeviceNotAvailableException {
    554         String command = "dpm set-device-owner --user " + userId + " '" + componentName + "'";
    555         String commandOutput = getDevice().executeShellCommand(command);
    556         boolean success = commandOutput.startsWith("Success:");
    557         // If we succeeded always log, if we are expecting failure don't log failures
    558         // as call stacks for passing tests confuse the logs.
    559         if (success || !expectFailure) {
    560             CLog.d("Output for command " + command + ": " + commandOutput);
    561         } else {
    562             CLog.d("Command Failed " + command);
    563         }
    564         return success;
    565     }
    566 
    567     protected void setDeviceOwnerOrFail(String componentName, int userId)
    568             throws Exception {
    569         assertTrue(setDeviceOwner(componentName, userId, /* expectFailure =*/ false));
    570     }
    571 
    572     protected void setDeviceOwnerExpectingFailure(String componentName, int userId)
    573             throws Exception {
    574         assertFalse(setDeviceOwner(componentName, userId, /* expectFailure =*/ true));
    575     }
    576 
    577     protected String getSettings(String namespace, String name, int userId)
    578             throws DeviceNotAvailableException {
    579         String command = "settings --user " + userId + " get " + namespace + " " + name;
    580         String commandOutput = getDevice().executeShellCommand(command);
    581         CLog.d("Output for command " + command + ": " + commandOutput);
    582         return commandOutput.replace("\n", "").replace("\r", "");
    583     }
    584 
    585     protected void putSettings(String namespace, String name, String value, int userId)
    586             throws DeviceNotAvailableException {
    587         String command = "settings --user " + userId + " put " + namespace + " " + name
    588                 + " " + value;
    589         String commandOutput = getDevice().executeShellCommand(command);
    590         CLog.d("Output for command " + command + ": " + commandOutput);
    591     }
    592 
    593     protected boolean removeAdmin(String componentName, int userId)
    594             throws DeviceNotAvailableException {
    595         String command = "dpm remove-active-admin --user " + userId + " '" + componentName + "'";
    596         String commandOutput = getDevice().executeShellCommand(command);
    597         CLog.d("Output for command " + command + ": " + commandOutput);
    598         return commandOutput.startsWith("Success:");
    599     }
    600 
    601     // Tries to remove and profile or device owners it finds.
    602     protected void removeOwners() throws DeviceNotAvailableException {
    603         String command = "dumpsys device_policy";
    604         String commandOutput = getDevice().executeShellCommand(command);
    605         String[] lines = commandOutput.split("\\r?\\n");
    606         for (int i = 0; i < lines.length; ++i) {
    607             String line = lines[i].trim();
    608             if (line.contains("Profile Owner")) {
    609                 // Line is "Profile owner (User <id>):
    610                 String[] tokens = line.split("\\(|\\)| ");
    611                 int userId = Integer.parseInt(tokens[4]);
    612                 i++;
    613                 line = lines[i].trim();
    614                 // Line is admin=ComponentInfo{<component>}
    615                 tokens = line.split("\\{|\\}");
    616                 String componentName = tokens[1];
    617                 CLog.w("Cleaning up profile owner " + userId + " " + componentName);
    618                 removeAdmin(componentName, userId);
    619             } else if (line.contains("Device Owner:")) {
    620                 i++;
    621                 line = lines[i].trim();
    622                 // Line is admin=ComponentInfo{<component>}
    623                 String[] tokens = line.split("\\{|\\}");
    624                 String componentName = tokens[1];
    625                 // Skip to user id line.
    626                 i += 3;
    627                 line = lines[i].trim();
    628                 // Line is User ID: <N>
    629                 tokens = line.split(":");
    630                 int userId = Integer.parseInt(tokens[1].trim());
    631                 CLog.w("Cleaning up device owner " + userId + " " + componentName);
    632                 removeAdmin(componentName, userId);
    633             }
    634         }
    635     }
    636 
    637     /**
    638      * Runs pm enable command to enable a package or component. Returns the command result.
    639      */
    640     protected String enableComponentOrPackage(int userId, String packageOrComponent)
    641             throws DeviceNotAvailableException {
    642         String command = "pm enable --user " + userId + " " + packageOrComponent;
    643         String result = getDevice().executeShellCommand(command);
    644         CLog.d("Output for command " + command + ": " + result);
    645         return result;
    646     }
    647 
    648     /**
    649      * Runs pm disable command to disable a package or component. Returns the command result.
    650      */
    651     protected String disableComponentOrPackage(int userId, String packageOrComponent)
    652             throws DeviceNotAvailableException {
    653         String command = "pm disable --user " + userId + " " + packageOrComponent;
    654         String result = getDevice().executeShellCommand(command);
    655         CLog.d("Output for command " + command + ": " + result);
    656         return result;
    657     }
    658 
    659     protected interface SuccessCondition {
    660         boolean check() throws Exception;
    661     }
    662 
    663     protected void assertUserGetsRemoved(int userId) throws Exception {
    664         tryWaitForSuccess(() -> !listUsers().contains(userId),
    665                 "The user " + userId + " has not been removed",
    666                 TIMEOUT_USER_REMOVED_MILLIS
    667                 );
    668     }
    669 
    670     protected void tryWaitForSuccess(SuccessCondition successCondition, String failureMessage,
    671             long timeoutMillis) throws Exception {
    672         long epoch = System.currentTimeMillis();
    673         while (System.currentTimeMillis() - epoch <= timeoutMillis) {
    674             Thread.sleep(WAIT_SAMPLE_INTERVAL_MILLIS);
    675             if (successCondition.check()) {
    676                 return;
    677             }
    678         }
    679         fail(failureMessage);
    680     }
    681 
    682     /**
    683      * Sets a user restriction via SetPolicyActivity.
    684      * <p>IMPORTANT: The package that contains SetPolicyActivity must have been installed prior to
    685      * calling this method.
    686      * @param key user restriction key
    687      * @param value true if we should set the restriction, false if we should clear it
    688      * @param userId userId to set/clear the user restriction on
    689      * @param packageName package where SetPolicyActivity is installed
    690      * @return The output of the command
    691      * @throws DeviceNotAvailableException
    692      */
    693     protected String changeUserRestriction(String key, boolean value, int userId,
    694             String packageName) throws DeviceNotAvailableException {
    695         return changePolicy(getUserRestrictionCommand(value),
    696                 " --es extra-restriction-key " + key, userId, packageName);
    697     }
    698 
    699     /**
    700      * Same as {@link #changeUserRestriction(String, boolean, int, String)} but asserts that it
    701      * succeeds.
    702      */
    703     protected void changeUserRestrictionOrFail(String key, boolean value, int userId,
    704             String packageName) throws DeviceNotAvailableException {
    705         changePolicyOrFail(getUserRestrictionCommand(value), " --es extra-restriction-key " + key,
    706                 userId, packageName);
    707     }
    708 
    709     /**
    710      * Sets some policy via SetPolicyActivity.
    711      * <p>IMPORTANT: The package that contains SetPolicyActivity must have been installed prior to
    712      * calling this method.
    713      * @param command command to pass to SetPolicyActivity
    714      * @param extras extras to pass to SetPolicyActivity
    715      * @param userId the userId where we invoke SetPolicyActivity
    716      * @param packageName where SetPolicyActivity is installed
    717      * @return The output of the command
    718      * @throws DeviceNotAvailableException
    719      */
    720     protected String changePolicy(String command, String extras, int userId, String packageName)
    721             throws DeviceNotAvailableException {
    722         String adbCommand = "am start -W --user " + userId
    723                 + " -c android.intent.category.DEFAULT "
    724                 + " --es extra-command " + command
    725                 + " " + extras
    726                 + " " + packageName + "/.SetPolicyActivity";
    727         String commandOutput = getDevice().executeShellCommand(adbCommand);
    728         CLog.d("Output for command " + adbCommand + ": " + commandOutput);
    729         return commandOutput;
    730     }
    731 
    732     /**
    733      * Same as {@link #changePolicy(String, String, int, String)} but asserts that it succeeds.
    734      */
    735     protected void changePolicyOrFail(String command, String extras, int userId,
    736             String packageName) throws DeviceNotAvailableException {
    737         String commandOutput = changePolicy(command, extras, userId, packageName);
    738         assertTrue("Command was expected to succeed " + commandOutput,
    739                 commandOutput.contains("Status: ok"));
    740     }
    741 
    742     private String getUserRestrictionCommand(boolean setRestriction) {
    743         if (setRestriction) {
    744             return "add-restriction";
    745         }
    746         return "clear-restriction";
    747     }
    748 
    749     /**
    750      * Set lockscreen password / work challenge for the given user, null or "" means clear
    751      */
    752     protected void changeUserCredential(String newCredential, String oldCredential, int userId)
    753             throws DeviceNotAvailableException {
    754         final String oldCredentialArgument = (oldCredential == null || oldCredential.isEmpty()) ? ""
    755                 : ("--old " + oldCredential);
    756         if (newCredential != null && !newCredential.isEmpty()) {
    757             String commandOutput = getDevice().executeShellCommand(String.format(
    758                     "cmd lock_settings set-password --user %d %s %s", userId, oldCredentialArgument,
    759                     newCredential));
    760             if (!commandOutput.startsWith("Password set to")) {
    761                 fail("Failed to set user credential: " + commandOutput);
    762             }
    763         } else {
    764             String commandOutput = getDevice().executeShellCommand(String.format(
    765                     "cmd lock_settings clear --user %d %s", userId, oldCredentialArgument));
    766             if (!commandOutput.startsWith("Lock credential cleared")) {
    767                 fail("Failed to clear user credential: " + commandOutput);
    768             }
    769         }
    770     }
    771 
    772     protected void wakeupAndDismissKeyguard() throws Exception {
    773         executeShellCommand("input keyevent KEYCODE_WAKEUP");
    774         executeShellCommand("wm dismiss-keyguard");
    775     }
    776 }
    777