• 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.cts.migration.MigrationHelper;
20 import com.android.ddmlib.Log.LogLevel;
21 import com.android.ddmlib.testrunner.InstrumentationResultParser;
22 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
23 import com.android.ddmlib.testrunner.TestIdentifier;
24 import com.android.ddmlib.testrunner.TestResult;
25 import com.android.ddmlib.testrunner.TestResult.TestStatus;
26 import com.android.ddmlib.testrunner.TestRunResult;
27 import com.android.tradefed.build.IBuildInfo;
28 import com.android.tradefed.device.DeviceNotAvailableException;
29 import com.android.tradefed.device.ITestDevice;
30 import com.android.tradefed.log.LogUtil.CLog;
31 import com.android.tradefed.result.CollectingTestListener;
32 import com.android.tradefed.testtype.DeviceTestCase;
33 import com.android.tradefed.testtype.IBuildReceiver;
34 
35 import java.io.File;
36 import java.io.FileNotFoundException;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collections;
40 import java.util.HashSet;
41 import java.util.Map;
42 import java.util.regex.Matcher;
43 import java.util.regex.Pattern;
44 
45 import javax.annotation.Nullable;
46 
47 /**
48  * Base class for device policy tests. It offers utility methods to run tests, set device or profile
49  * owner, etc.
50  */
51 public class BaseDevicePolicyTest extends DeviceTestCase implements IBuildReceiver {
52 
53     private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
54 
55     protected static final int USER_SYSTEM = 0; // From the UserHandle class.
56 
57     protected static final int USER_OWNER = 0;
58 
59     // From the UserInfo class
60     protected static final int FLAG_PRIMARY = 0x00000001;
61     protected static final int FLAG_GUEST = 0x00000004;
62     protected static final int FLAG_EPHEMERAL = 0x00000100;
63 
64     protected static interface Settings {
65         public static final String GLOBAL_NAMESPACE = "global";
66         public static interface Global {
67             public static final String DEVICE_PROVISIONED = "device_provisioned";
68         }
69     }
70 
71     protected IBuildInfo mCtsBuild;
72 
73     private String mPackageVerifier;
74     private HashSet<String> mAvailableFeatures;
75 
76     /** Whether DPM is supported. */
77     protected boolean mHasFeature;
78     protected int mPrimaryUserId;
79 
80     /** Whether multi-user is supported. */
81     protected boolean mSupportsMultiUser;
82 
83     /** Users we shouldn't delete in the tests */
84     private ArrayList<Integer> mFixedUsers;
85 
86     @Override
setBuild(IBuildInfo buildInfo)87     public void setBuild(IBuildInfo buildInfo) {
88         mCtsBuild = buildInfo;
89     }
90 
91     @Override
setUp()92     protected void setUp() throws Exception {
93         super.setUp();
94         assertNotNull(mCtsBuild);  // ensure build has been set before test is run.
95         mHasFeature = getDevice().getApiLevel() >= 21 /* Build.VERSION_CODES.L */
96                 && hasDeviceFeature("android.software.device_admin");
97         mSupportsMultiUser = getMaxNumberOfUsersSupported() > 1;
98 
99         // disable the package verifier to avoid the dialog when installing an app
100         mPackageVerifier = getDevice().executeShellCommand(
101                 "settings get global package_verifier_enable");
102         getDevice().executeShellCommand("settings put global package_verifier_enable 0");
103 
104         mFixedUsers = new ArrayList();
105         mPrimaryUserId = getPrimaryUser();
106         mFixedUsers.add(mPrimaryUserId);
107         if (mPrimaryUserId != USER_SYSTEM) {
108             mFixedUsers.add(USER_SYSTEM);
109         }
110         switchUser(mPrimaryUserId);
111         removeOwners();
112         removeTestUsers();
113     }
114 
115     @Override
tearDown()116     protected void tearDown() throws Exception {
117         // reset the package verifier setting to its original value
118         getDevice().executeShellCommand("settings put global package_verifier_enable "
119                 + mPackageVerifier);
120         removeOwners();
121         removeTestUsers();
122         super.tearDown();
123     }
124 
installAppAsUser(String appFileName, int userId)125     protected void installAppAsUser(String appFileName, int userId) throws FileNotFoundException,
126             DeviceNotAvailableException {
127         installAppAsUser(appFileName, true, userId);
128     }
129 
installAppAsUser(String appFileName, boolean grantPermissions, int userId)130     protected void installAppAsUser(String appFileName, boolean grantPermissions, int userId)
131             throws FileNotFoundException, DeviceNotAvailableException {
132         CLog.d("Installing app " + appFileName + " for user " + userId);
133         String result = getDevice().installPackageForUser(
134                 MigrationHelper.getTestFile(mCtsBuild, appFileName), true, grantPermissions,
135                 userId, "-t");
136         assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result,
137                 result);
138     }
139 
forceStopPackageForUser(String packageName, int userId)140     protected void forceStopPackageForUser(String packageName, int userId) throws Exception {
141         // TODO Move this logic to ITestDevice
142         executeShellCommand("am force-stop --user " + userId + " " + packageName);
143     }
144 
executeShellCommand(final String command)145     private void executeShellCommand(final String command) throws Exception {
146         CLog.d("Starting command " + command);
147         String commandOutput = getDevice().executeShellCommand(command);
148         CLog.d("Output for command " + command + ": " + commandOutput);
149     }
150 
151     /** Initializes the user with the given id. This is required so that apps can run on it. */
startUser(int userId)152     protected void startUser(int userId) throws Exception {
153         getDevice().startUser(userId);
154     }
155 
switchUser(int userId)156     protected void switchUser(int userId) throws Exception {
157         // TODO Move this logic to ITestDevice
158         executeShellCommand("am switch-user " + userId);
159     }
160 
getMaxNumberOfUsersSupported()161     protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
162         return getDevice().getMaxNumberOfUsersSupported();
163     }
164 
getUserFlags(int userId)165     protected int getUserFlags(int userId) throws DeviceNotAvailableException {
166         String command = "pm list users";
167         String commandOutput = getDevice().executeShellCommand(command);
168         CLog.i("Output for command " + command + ": " + commandOutput);
169 
170         String[] lines = commandOutput.split("\\r?\\n");
171         assertTrue(commandOutput + " should contain at least one line", lines.length >= 1);
172         for (int i = 1; i < lines.length; i++) {
173             // Individual user is printed out like this:
174             // \tUserInfo{$id$:$name$:$Integer.toHexString(flags)$} [running]
175             String[] tokens = lines[i].split("\\{|\\}|:");
176             assertTrue(lines[i] + " doesn't contain 4 or 5 tokens",
177                     tokens.length == 4 || tokens.length == 5);
178             // If the user IDs match, return the flags.
179             if (Integer.parseInt(tokens[1]) == userId) {
180                 return Integer.parseInt(tokens[3], 16);
181             }
182         }
183         fail("User not found");
184         return 0;
185     }
186 
listUsers()187     protected ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
188         return getDevice().listUsers();
189     }
190 
stopUser(int userId)191     protected void stopUser(int userId) throws Exception  {
192         String stopUserCommand = "am stop-user -w -f " + userId;
193         CLog.d("starting command \"" + stopUserCommand + "\" and waiting.");
194         CLog.d("Output for command " + stopUserCommand + ": "
195                 + getDevice().executeShellCommand(stopUserCommand));
196     }
197 
removeUser(int userId)198     protected void removeUser(int userId) throws Exception  {
199         if (listUsers().contains(userId) && userId != USER_SYSTEM) {
200             // Don't log output, as tests sometimes set no debug user restriction, which
201             // causes this to fail, we should still continue and remove the user.
202             String stopUserCommand = "am stop-user -w -f " + userId;
203             CLog.d("stopping and removing user " + userId);
204             getDevice().executeShellCommand(stopUserCommand);
205             assertTrue("Couldn't remove user", getDevice().removeUser(userId));
206         }
207     }
208 
removeTestUsers()209     protected void removeTestUsers() throws Exception {
210         for (int userId : listUsers()) {
211             if (!mFixedUsers.contains(userId)) {
212                 removeUser(userId);
213             }
214         }
215     }
216 
217     /** Returns true if the specified tests passed. Tests are run as given user. */
runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, int userId)218     protected boolean runDeviceTestsAsUser(
219             String pkgName, @Nullable String testClassName, int userId)
220             throws DeviceNotAvailableException {
221         return runDeviceTestsAsUser(pkgName, testClassName, null /*testMethodName*/, userId);
222     }
223 
224     /** Returns true if the specified tests passed. Tests are run as given user. */
runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, String testMethodName, int userId)225     protected boolean runDeviceTestsAsUser(
226             String pkgName, @Nullable String testClassName, String testMethodName, int userId)
227             throws DeviceNotAvailableException {
228         Map<String, String> params = Collections.emptyMap();
229         return runDeviceTestsAsUser(pkgName, testClassName, testMethodName, userId, params);
230     }
231 
runDeviceTestsAsUser(String pkgName, @Nullable String testClassName, @Nullable String testMethodName, int userId, Map<String, String> params)232     protected boolean runDeviceTestsAsUser(String pkgName, @Nullable String testClassName,
233             @Nullable String testMethodName, int userId,
234             Map<String, String> params) throws DeviceNotAvailableException {
235         if (testClassName != null && testClassName.startsWith(".")) {
236             testClassName = pkgName + testClassName;
237         }
238 
239         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
240                 pkgName, RUNNER, getDevice().getIDevice());
241         if (testClassName != null && testMethodName != null) {
242             testRunner.setMethodName(testClassName, testMethodName);
243         } else if (testClassName != null) {
244             testRunner.setClassName(testClassName);
245         }
246 
247         for (Map.Entry<String, String> param : params.entrySet()) {
248             testRunner.addInstrumentationArg(param.getKey(), param.getValue());
249         }
250 
251         CollectingTestListener listener = new CollectingTestListener();
252         assertTrue(getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener));
253 
254         TestRunResult runResult = listener.getCurrentRunResults();
255         printTestResult(runResult);
256         return !runResult.hasFailedTests() && runResult.getNumTestsInState(TestStatus.PASSED) > 0;
257     }
258 
259     /** Returns true if the system supports the split between system and primary user. */
hasUserSplit()260     protected boolean hasUserSplit() throws DeviceNotAvailableException {
261         return getBooleanSystemProperty("ro.fw.system_user_split", false);
262     }
263 
264     /** Returns a boolean value of the system property with the specified key. */
getBooleanSystemProperty(String key, boolean defaultValue)265     protected boolean getBooleanSystemProperty(String key, boolean defaultValue)
266             throws DeviceNotAvailableException {
267         final String[] positiveValues = {"1", "y", "yes", "true", "on"};
268         final String[] negativeValues = {"0", "n", "no", "false", "off"};
269         String propertyValue = getDevice().getProperty(key);
270         if (propertyValue == null || propertyValue.isEmpty()) {
271             return defaultValue;
272         }
273         if (Arrays.asList(positiveValues).contains(propertyValue)) {
274             return true;
275         }
276         if (Arrays.asList(negativeValues).contains(propertyValue)) {
277             return false;
278         }
279         fail("Unexpected value of boolean system property '" + key + "': " + propertyValue);
280         return false;
281     }
282 
283     /** Checks whether it is possible to create the desired number of users. */
canCreateAdditionalUsers(int numberOfUsers)284     protected boolean canCreateAdditionalUsers(int numberOfUsers)
285             throws DeviceNotAvailableException {
286         return listUsers().size() + numberOfUsers <= getMaxNumberOfUsersSupported();
287     }
288 
printTestResult(TestRunResult runResult)289     private void printTestResult(TestRunResult runResult) {
290         for (Map.Entry<TestIdentifier, TestResult> testEntry :
291                 runResult.getTestResults().entrySet()) {
292             TestResult testResult = testEntry.getValue();
293             CLog.d("Test " + testEntry.getKey() + ": " + testResult.getStatus());
294             if (testResult.getStatus() != TestStatus.PASSED) {
295                 CLog.d(testResult.getStackTrace());
296             }
297         }
298     }
299 
hasDeviceFeature(String requiredFeature)300     protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
301         if (mAvailableFeatures == null) {
302             // TODO: Move this logic to ITestDevice.
303             String command = "pm list features";
304             String commandOutput = getDevice().executeShellCommand(command);
305             CLog.i("Output for command " + command + ": " + commandOutput);
306 
307             // Extract the id of the new user.
308             mAvailableFeatures = new HashSet<>();
309             for (String feature: commandOutput.split("\\s+")) {
310                 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
311                 String[] tokens = feature.split(":");
312                 assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
313                         tokens.length > 1);
314                 assertEquals(feature, "feature", tokens[0]);
315                 mAvailableFeatures.add(tokens[1]);
316             }
317         }
318         boolean result = mAvailableFeatures.contains(requiredFeature);
319         if (!result) {
320             CLog.d("Device doesn't have required feature "
321             + requiredFeature + ". Test won't run.");
322         }
323         return result;
324     }
325 
createUser()326     protected int createUser() throws Exception {
327         int userId = createUser(0);
328         // TODO remove this and audit tests so they start users as necessary
329         startUser(userId);
330         return userId;
331     }
332 
createUser(int flags)333     protected int createUser(int flags) throws Exception {
334         boolean guest = FLAG_GUEST == (flags & FLAG_GUEST);
335         boolean ephemeral = FLAG_EPHEMERAL == (flags & FLAG_EPHEMERAL);
336         // TODO Use ITestDevice.createUser() when guest and ephemeral is available
337         String command ="pm create-user " + (guest ? "--guest " : "")
338                 + (ephemeral ? "--ephemeral " : "") + "TestUser_" + System.currentTimeMillis();
339         CLog.d("Starting command " + command);
340         String commandOutput = getDevice().executeShellCommand(command);
341         CLog.d("Output for command " + command + ": " + commandOutput);
342 
343         // Extract the id of the new user.
344         String[] tokens = commandOutput.split("\\s+");
345         assertTrue(tokens.length > 0);
346         assertEquals("Success:", tokens[0]);
347         return Integer.parseInt(tokens[tokens.length-1]);
348     }
349 
createManagedProfile(int parentUserId)350     protected int createManagedProfile(int parentUserId) throws DeviceNotAvailableException {
351         String command = "pm create-user --profileOf " + parentUserId + " --managed "
352                 + "TestProfile_" + System.currentTimeMillis();
353         CLog.d("Starting command " + command);
354         String commandOutput = getDevice().executeShellCommand(command);
355         CLog.d("Output for command " + command + ": " + commandOutput);
356 
357         // Extract the id of the new user.
358         String[] tokens = commandOutput.split("\\s+");
359         assertTrue(commandOutput + " expected to have format \"Success: {USER_ID}\"",
360                 tokens.length > 0);
361         assertEquals(commandOutput, "Success:", tokens[0]);
362         return Integer.parseInt(tokens[tokens.length-1]);
363     }
364 
getPrimaryUser()365     protected int getPrimaryUser() throws DeviceNotAvailableException {
366         return getDevice().getPrimaryUserId();
367     }
368 
getUserSerialNumber(int userId)369     protected int getUserSerialNumber(int userId) throws DeviceNotAvailableException{
370         // TODO: Move this logic to ITestDevice.
371         // dumpsys user return lines like "UserInfo{0:Owner:13} serialNo=0"
372         String commandOutput = getDevice().executeShellCommand("dumpsys user");
373         String[] tokens = commandOutput.split("\\n");
374         for (String token : tokens) {
375             token = token.trim();
376             if (token.contains("UserInfo{" + userId + ":")) {
377                 String[] split = token.split("serialNo=");
378                 assertTrue(split.length == 2);
379                 int serialNumber = Integer.parseInt(split[1]);
380                 CLog.d("Serial number of user " + userId + ": "
381                         + serialNumber);
382                 return serialNumber;
383             }
384         }
385         fail("Couldn't find user " + userId);
386         return -1;
387     }
388 
setProfileOwner(String componentName, int userId, boolean expectFailure)389     protected boolean setProfileOwner(String componentName, int userId, boolean expectFailure)
390             throws DeviceNotAvailableException {
391         String command = "dpm set-profile-owner --user " + userId + " '" + componentName + "'";
392         String commandOutput = getDevice().executeShellCommand(command);
393         boolean success = commandOutput.startsWith("Success:");
394         // If we succeeded always log, if we are expecting failure don't log failures
395         // as call stacks for passing tests confuse the logs.
396         if (success || !expectFailure) {
397             CLog.d("Output for command " + command + ": " + commandOutput);
398         } else {
399             CLog.d("Command Failed " + command);
400         }
401         return success;
402     }
403 
setProfileOwnerOrFail(String componentName, int userId)404     protected void setProfileOwnerOrFail(String componentName, int userId)
405             throws Exception {
406         if (!setProfileOwner(componentName, userId, /*expectFailure*/ false)) {
407             removeUser(userId);
408             fail("Failed to set profile owner");
409         }
410     }
411 
setDeviceAdminInner(String componentName, int userId)412     private String setDeviceAdminInner(String componentName, int userId)
413             throws DeviceNotAvailableException {
414         String command = "dpm set-active-admin --user " + userId + " '" + componentName + "'";
415         String commandOutput = getDevice().executeShellCommand(command);
416         return commandOutput;
417     }
418 
setDeviceAdmin(String componentName, int userId)419     protected void setDeviceAdmin(String componentName, int userId)
420             throws DeviceNotAvailableException {
421         String commandOutput = setDeviceAdminInner(componentName, userId);
422         CLog.d("Output for command " + commandOutput
423                 + ": " + commandOutput);
424         assertTrue(commandOutput + " expected to start with \"Success:\"",
425                 commandOutput.startsWith("Success:"));
426     }
427 
setDeviceAdminExpectingFailure(String componentName, int userId, String errorMessage)428     protected void setDeviceAdminExpectingFailure(String componentName, int userId,
429             String errorMessage) throws DeviceNotAvailableException {
430         String commandOutput = setDeviceAdminInner(componentName, userId);
431         if (!commandOutput.contains(errorMessage)) {
432             fail(commandOutput + " expected to contain \"" + errorMessage + "\"");
433         }
434     }
435 
setDeviceOwner(String componentName, int userId, boolean expectFailure)436     protected boolean setDeviceOwner(String componentName, int userId, boolean expectFailure)
437             throws DeviceNotAvailableException {
438         String command = "dpm set-device-owner --user " + userId + " '" + componentName + "'";
439         String commandOutput = getDevice().executeShellCommand(command);
440         boolean success = commandOutput.startsWith("Success:");
441         // If we succeeded always log, if we are expecting failure don't log failures
442         // as call stacks for passing tests confuse the logs.
443         if (success || !expectFailure) {
444             CLog.d("Output for command " + command + ": " + commandOutput);
445         } else {
446             CLog.d("Command Failed " + command);
447         }
448         return success;
449     }
450 
getSettings(String namespace, String name, int userId)451     protected String getSettings(String namespace, String name, int userId)
452             throws DeviceNotAvailableException {
453         String command = "settings --user " + userId + " get " + namespace + " " + name;
454         String commandOutput = getDevice().executeShellCommand(command);
455         CLog.d("Output for command " + command + ": " + commandOutput);
456         return commandOutput.replace("\n", "").replace("\r", "");
457     }
458 
putSettings(String namespace, String name, String value, int userId)459     protected void putSettings(String namespace, String name, String value, int userId)
460             throws DeviceNotAvailableException {
461         String command = "settings --user " + userId + " put " + namespace + " " + name
462                 + " " + value;
463         String commandOutput = getDevice().executeShellCommand(command);
464         CLog.d("Output for command " + command + ": " + commandOutput);
465     }
466 
removeAdmin(String componentName, int userId)467     protected boolean removeAdmin(String componentName, int userId)
468             throws DeviceNotAvailableException {
469         String command = "dpm remove-active-admin --user " + userId + " '" + componentName + "'";
470         String commandOutput = getDevice().executeShellCommand(command);
471         CLog.d("Output for command " + command + ": " + commandOutput);
472         return commandOutput.startsWith("Success:");
473     }
474 
475     // Tries to remove and profile or device owners it finds.
removeOwners()476     protected void removeOwners() throws DeviceNotAvailableException {
477         String command = "dumpsys device_policy";
478         String commandOutput = getDevice().executeShellCommand(command);
479         String[] lines = commandOutput.split("\\r?\\n");
480         for (int i = 0; i < lines.length; ++i) {
481             String line = lines[i].trim();
482             if (line.contains("Profile Owner")) {
483                 // Line is "Profile owner (User <id>):
484                 String[] tokens = line.split("\\(|\\)| ");
485                 int userId = Integer.parseInt(tokens[4]);
486                 i++;
487                 line = lines[i].trim();
488                 // Line is admin=ComponentInfo{<component>}
489                 tokens = line.split("\\{|\\}");
490                 String componentName = tokens[1];
491                 CLog.w("Cleaning up profile owner " + userId + " " + componentName);
492                 removeAdmin(componentName, userId);
493             } else if (line.contains("Device Owner:")) {
494                 i++;
495                 line = lines[i].trim();
496                 // Line is admin=ComponentInfo{<component>}
497                 String[] tokens = line.split("\\{|\\}");
498                 String componentName = tokens[1];
499                 // Skip to user id line.
500                 i += 3;
501                 line = lines[i].trim();
502                 // Line is User ID: <N>
503                 tokens = line.split(":");
504                 int userId = Integer.parseInt(tokens[1].trim());
505                 CLog.w("Cleaning up device owner " + userId + " " + componentName);
506                 removeAdmin(componentName, userId);
507             }
508         }
509     }
510 }
511