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
setBuild(IBuildInfo buildInfo)110     public void setBuild(IBuildInfo buildInfo) {
111         mCtsBuild = buildInfo;
112     }
113 
114     @Override
setUp()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
tearDown()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 
installAppAsUser(String appFileName, int userId)153     protected void installAppAsUser(String appFileName, int userId) throws FileNotFoundException,
154             DeviceNotAvailableException {
155         installAppAsUser(appFileName, true, userId);
156     }
157 
installAppAsUser(String appFileName, boolean grantPermissions, int userId)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 
forceStopPackageForUser(String packageName, int userId)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 
executeShellCommand(final String command)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. */
startUser(int userId)180     protected void startUser(int userId) throws Exception {
181         getDevice().startUser(userId);
182     }
183 
switchUser(int userId)184     protected void switchUser(int userId) throws Exception {
185         // TODO Move this logic to ITestDevice
186         executeShellCommand("am switch-user " + userId);
187     }
188 
getMaxNumberOfUsersSupported()189     protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
190         return getDevice().getMaxNumberOfUsersSupported();
191     }
192 
getUserFlags(int userId)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 
listUsers()215     protected ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
216         return getDevice().listUsers();
217     }
218 
getFirstManagedProfileUserId()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 
stopUser(int userId)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 
waitForBroadcastIdle()238     private void waitForBroadcastIdle() throws Exception {
239         getDevice().executeShellCommand("am wait-for-broadcast-idle");
240     }
241 
removeUser(int userId)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 
removeTestUsers()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      */
getUsersCreatedByTests()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. */
removeTestPackages()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 
runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, int userId)279     protected void runDeviceTestsAsUser(
280             String pkgName, @Nullable String testClassName, int userId)
281             throws DeviceNotAvailableException {
282         runDeviceTestsAsUser(pkgName, testClassName, null /*testMethodName*/, userId);
283     }
284 
runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, String testMethodName, int userId)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 
runDeviceTests( String pkgName, @Nullable String testClassName, String testMethodName)292     protected void runDeviceTests(
293             String pkgName, @Nullable String testClassName, String testMethodName)
294             throws DeviceNotAvailableException {
295         runDeviceTestsAsUser(pkgName, testClassName, testMethodName, mPrimaryUserId);
296     }
297 
runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, @Nullable String testMethodName, int userId, Map<String, String> params)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. */
rebootAndWaitUntilReady()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. */
hasUserSplit()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. */
getBooleanSystemProperty(String key, boolean defaultValue)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. */
canCreateAdditionalUsers(int numberOfUsers)379     protected boolean canCreateAdditionalUsers(int numberOfUsers)
380             throws DeviceNotAvailableException {
381         return listUsers().size() + numberOfUsers <= getMaxNumberOfUsersSupported();
382     }
383 
hasDeviceFeature(String requiredFeature)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 
createUser()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 
createUser(int flags)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 
createManagedProfile(int parentUserId)434     protected int createManagedProfile(int parentUserId) throws DeviceNotAvailableException {
435         String commandOutput = getCreateManagedProfileCommandOutput(parentUserId);
436         return getUserIdFromCreateUserCommandOutput(commandOutput);
437     }
438 
assertCannotCreateManagedProfile(int parentUserId)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 
getUserIdFromCreateUserCommandOutput(String commandOutput)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 
getCreateManagedProfileCommandOutput(int parentUserId)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 
getPrimaryUser()469     protected int getPrimaryUser() throws DeviceNotAvailableException {
470         return getDevice().getPrimaryUserId();
471     }
472 
getUserSerialNumber(int userId)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 
setProfileOwner(String componentName, int userId, boolean expectFailure)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 
setProfileOwnerOrFail(String componentName, int userId)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 
setProfileOwnerExpectingFailure(String componentName, int userId)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 
setDeviceAdminInner(String componentName, int userId)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 
setDeviceAdmin(String componentName, int userId)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 
setDeviceAdminExpectingFailure(String componentName, int userId, String errorMessage)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 
setDeviceOwner(String componentName, int userId, boolean expectFailure)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 
setDeviceOwnerOrFail(String componentName, int userId)567     protected void setDeviceOwnerOrFail(String componentName, int userId)
568             throws Exception {
569         assertTrue(setDeviceOwner(componentName, userId, /* expectFailure =*/ false));
570     }
571 
setDeviceOwnerExpectingFailure(String componentName, int userId)572     protected void setDeviceOwnerExpectingFailure(String componentName, int userId)
573             throws Exception {
574         assertFalse(setDeviceOwner(componentName, userId, /* expectFailure =*/ true));
575     }
576 
getSettings(String namespace, String name, int userId)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 
putSettings(String namespace, String name, String value, int userId)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 
removeAdmin(String componentName, int userId)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.
removeOwners()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      */
enableComponentOrPackage(int userId, String packageOrComponent)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      */
disableComponentOrPackage(int userId, String packageOrComponent)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 {
check()660         boolean check() throws Exception;
661     }
662 
assertUserGetsRemoved(int userId)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 
tryWaitForSuccess(SuccessCondition successCondition, String failureMessage, long timeoutMillis)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      */
changeUserRestriction(String key, boolean value, int userId, String packageName)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      */
changeUserRestrictionOrFail(String key, boolean value, int userId, String packageName)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      */
changePolicy(String command, String extras, int userId, String packageName)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      */
changePolicyOrFail(String command, String extras, int userId, String packageName)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 
getUserRestrictionCommand(boolean setRestriction)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      */
changeUserCredential(String newCredential, String oldCredential, int userId)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 
wakeupAndDismissKeyguard()772     protected void wakeupAndDismissKeyguard() throws Exception {
773         executeShellCommand("input keyevent KEYCODE_WAKEUP");
774         executeShellCommand("wm dismiss-keyguard");
775     }
776 }
777