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