• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
setBuild(IBuildInfo buildInfo)126     public void setBuild(IBuildInfo buildInfo) {
127         mCtsBuild = buildInfo;
128     }
129 
130     @Override
setUp()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
tearDown()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 
installAppAsUser(String appFileName, int userId)173     protected void installAppAsUser(String appFileName, int userId) throws FileNotFoundException,
174             DeviceNotAvailableException {
175         installAppAsUser(appFileName, true, userId);
176     }
177 
installAppAsUser(String appFileName, boolean grantPermissions, int userId)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 
forceStopPackageForUser(String packageName, int userId)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 
executeShellCommand(final String command)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. */
startUser(int userId)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      */
switchUser(int userId)211     protected void switchUser(int userId) throws Exception {
212         // TODO Move this logic to ITestDevice
213         executeShellCommand("am switch-user " + userId);
214     }
215 
getMaxNumberOfUsersSupported()216     protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
217         return getDevice().getMaxNumberOfUsersSupported();
218     }
219 
getMaxNumberOfRunningUsersSupported()220     protected int getMaxNumberOfRunningUsersSupported() throws DeviceNotAvailableException {
221         return getDevice().getMaxNumberOfRunningUsersSupported();
222     }
223 
getUserFlags(int userId)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 
listUsers()246     protected ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
247         return getDevice().listUsers();
248     }
249 
listRunningUsers()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 
getFirstManagedProfileUserId()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 
stopUserAsync(int userId)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 
stopUser(int userId)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 
waitForBroadcastIdle()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 
removeUser(int userId)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 
removeTestUsers()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      */
getUsersCreatedByTests()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. */
removeTestPackages()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 
runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, int userId)369     protected void runDeviceTestsAsUser(
370             String pkgName, @Nullable String testClassName, int userId)
371             throws DeviceNotAvailableException {
372         runDeviceTestsAsUser(pkgName, testClassName, null /*testMethodName*/, userId);
373     }
374 
runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, String testMethodName, int userId)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 
runDeviceTests( String pkgName, @Nullable String testClassName, String testMethodName)382     protected void runDeviceTests(
383             String pkgName, @Nullable String testClassName, String testMethodName)
384             throws DeviceNotAvailableException {
385         runDeviceTestsAsUser(pkgName, testClassName, testMethodName, mPrimaryUserId);
386     }
387 
runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, @Nullable String testMethodName, int userId, Map<String, String> params)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. */
rebootAndWaitUntilReady()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. */
hasUserSplit()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. */
getBooleanSystemProperty(String key, boolean defaultValue)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. */
canCreateAdditionalUsers(int numberOfUsers)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. */
canStartAdditionalUsers(int numberOfUsers)475     protected boolean canStartAdditionalUsers(int numberOfUsers)
476             throws DeviceNotAvailableException {
477         return listRunningUsers().size() + numberOfUsers <= getMaxNumberOfRunningUsersSupported();
478     }
479 
hasDeviceFeature(String requiredFeature)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 
createUser()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 
createUser(int flags)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 
createManagedProfile(int parentUserId)530     protected int createManagedProfile(int parentUserId) throws DeviceNotAvailableException {
531         String commandOutput = getCreateManagedProfileCommandOutput(parentUserId);
532         return getUserIdFromCreateUserCommandOutput(commandOutput);
533     }
534 
assertCannotCreateManagedProfile(int parentUserId)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 
getUserIdFromCreateUserCommandOutput(String commandOutput)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 
getCreateManagedProfileCommandOutput(int parentUserId)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 
getPrimaryUser()565     protected int getPrimaryUser() throws DeviceNotAvailableException {
566         return getDevice().getPrimaryUserId();
567     }
568 
getUserSerialNumber(int userId)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 
setProfileOwner(String componentName, int userId, boolean expectFailure)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 
setProfileOwnerOrFail(String componentName, int userId)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 
setProfileOwnerExpectingFailure(String componentName, int userId)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 
setDeviceAdminInner(String componentName, int userId)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 
setDeviceAdmin(String componentName, int userId)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 
setDeviceAdminExpectingFailure(String componentName, int userId, String errorMessage)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 
setDeviceOwner(String componentName, int userId, boolean expectFailure)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 
setDeviceOwnerOrFail(String componentName, int userId)663     protected void setDeviceOwnerOrFail(String componentName, int userId)
664             throws Exception {
665         assertTrue(setDeviceOwner(componentName, userId, /* expectFailure =*/ false));
666     }
667 
setDeviceOwnerExpectingFailure(String componentName, int userId)668     protected void setDeviceOwnerExpectingFailure(String componentName, int userId)
669             throws Exception {
670         assertFalse(setDeviceOwner(componentName, userId, /* expectFailure =*/ true));
671     }
672 
getSettings(String namespace, String name, int userId)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 
putSettings(String namespace, String name, String value, int userId)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 
removeAdmin(String componentName, int userId)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.
removeOwners()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      */
enableComponentOrPackage(int userId, String packageOrComponent)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      */
disableComponentOrPackage(int userId, String packageOrComponent)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 {
check()756         boolean check() throws Exception;
757     }
758 
assertUserGetsRemoved(int userId)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 
tryWaitForSuccess(SuccessCondition successCondition, String failureMessage, long timeoutMillis)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      */
changeUserRestriction(String key, boolean value, int userId, String packageName)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      */
changeUserRestrictionOrFail(String key, boolean value, int userId, String packageName)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      */
changePolicy(String command, String extras, int userId, String packageName)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      */
changePolicyOrFail(String command, String extras, int userId, String packageName)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 
getUserRestrictionCommand(boolean setRestriction)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      */
changeUserCredential(String newCredential, String oldCredential, int userId)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      */
verifyUserCredential(String credential, int userId)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 
wakeupAndDismissKeyguard()885     protected void wakeupAndDismissKeyguard() throws Exception {
886         executeShellCommand("input keyevent KEYCODE_WAKEUP");
887         executeShellCommand("wm dismiss-keyguard");
888     }
889 
startActivityAsUser(int userId, String packageName, String activityName)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 
getDefaultLauncher()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