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