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.tradefed.build.CtsBuildHelper;
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.HashSet;
39 import java.util.Map;
40 
41 import javax.annotation.Nullable;
42 
43 /**
44  * Base class for device policy tests. It offers utility methods to run tests, set device or profile
45  * owner, etc.
46  */
47 public class BaseDevicePolicyTest extends DeviceTestCase implements IBuildReceiver {
48 
49     private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
50 
51     protected CtsBuildHelper mCtsBuild;
52 
53     private String mPackageVerifier;
54     private HashSet<String> mAvailableFeatures;
55     protected boolean mHasFeature;
56 
57     @Override
setBuild(IBuildInfo buildInfo)58     public void setBuild(IBuildInfo buildInfo) {
59         mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
60     }
61 
62     @Override
setUp()63     protected void setUp() throws Exception {
64         super.setUp();
65         assertNotNull(mCtsBuild);  // ensure build has been set before test is run.
66         mHasFeature = getDevice().getApiLevel() >= 21 /* Build.VERSION_CODES.L */
67                 && hasDeviceFeature("android.software.device_admin");
68         // disable the package verifier to avoid the dialog when installing an app
69         mPackageVerifier = getDevice().executeShellCommand(
70                 "settings get global package_verifier_enable");
71         getDevice().executeShellCommand("settings put global package_verifier_enable 0");
72     }
73 
74     @Override
tearDown()75     protected void tearDown() throws Exception {
76         // reset the package verifier setting to its original value
77         getDevice().executeShellCommand("settings put global package_verifier_enable "
78                 + mPackageVerifier);
79         super.tearDown();
80     }
81 
installApp(String fileName)82     protected void installApp(String fileName)
83             throws FileNotFoundException, DeviceNotAvailableException {
84         CLog.logAndDisplay(LogLevel.INFO, "Installing app " + fileName);
85         String installResult = getDevice().installPackage(mCtsBuild.getTestApp(fileName), true);
86         assertNull(String.format("Failed to install %s, Reason: %s", fileName, installResult),
87                 installResult);
88     }
89 
installAppAsUser(String appFileName, int userId)90     protected void installAppAsUser(String appFileName, int userId) throws FileNotFoundException,
91             DeviceNotAvailableException {
92         final ITestDevice device = getDevice();
93 
94         final File apk = mCtsBuild.getTestApp(appFileName);
95         final String remotePath = "/data/local/tmp/" + apk.getName();
96         if (!device.pushFile(apk, remotePath)) {
97             throw new IllegalStateException("Failed to push " + apk);
98         }
99 
100         final String result = device.executeShellCommand(
101                 "pm install -r --user " + userId + " " + remotePath);
102         assertTrue(result, result.contains("\nSuccess"));
103     }
104 
105     /** Initializes the user with the given id. This is required so that apps can run on it. */
startUser(int userId)106     protected void startUser(int userId) throws Exception {
107         String command = "am start-user " + userId;
108         CLog.logAndDisplay(LogLevel.INFO, "Starting command " + command);
109         String commandOutput = getDevice().executeShellCommand(command);
110         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
111         assertTrue(commandOutput + " expected to start with \"Success:\"",
112                 commandOutput.startsWith("Success:"));
113     }
114 
getMaxNumberOfUsersSupported()115     protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
116         // TODO: move this to ITestDevice once it supports users
117         String command = "pm get-max-users";
118         String commandOutput = getDevice().executeShellCommand(command);
119         CLog.i("Output for command " + command + ": " + commandOutput);
120 
121         try {
122             return Integer.parseInt(commandOutput.substring(commandOutput.lastIndexOf(" ")).trim());
123         } catch (NumberFormatException e) {
124             fail("Failed to parse result: " + commandOutput);
125         }
126         return 0;
127     }
128 
listUsers()129     protected ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
130         String command = "pm list users";
131         String commandOutput = getDevice().executeShellCommand(command);
132         CLog.i("Output for command " + command + ": " + commandOutput);
133 
134         // Extract the id of all existing users.
135         String[] lines = commandOutput.split("\\r?\\n");
136         assertTrue(commandOutput + " should contain at least one line", lines.length >= 1);
137         assertEquals(commandOutput, lines[0], "Users:");
138 
139         ArrayList<Integer> users = new ArrayList<Integer>();
140         for (int i = 1; i < lines.length; i++) {
141             // Individual user is printed out like this:
142             // \tUserInfo{$id$:$name$:$Integer.toHexString(flags)$} [running]
143             String[] tokens = lines[i].split("\\{|\\}|:");
144             assertTrue(lines[i] + " doesn't contain 4 or 5 tokens",
145                     tokens.length == 4 || tokens.length == 5);
146             users.add(Integer.parseInt(tokens[1]));
147         }
148         return users;
149     }
150 
removeUser(int userId)151     protected void removeUser(int userId) throws Exception  {
152         String stopUserCommand = "am stop-user -w " + userId;
153         CLog.logAndDisplay(LogLevel.INFO, "starting command \"" + stopUserCommand + "\" and waiting.");
154         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + stopUserCommand + ": "
155                 + getDevice().executeShellCommand(stopUserCommand));
156         String removeUserCommand = "pm remove-user " + userId;
157         CLog.logAndDisplay(LogLevel.INFO, "starting command " + removeUserCommand);
158         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + removeUserCommand + ": "
159                 + getDevice().executeShellCommand(removeUserCommand));
160     }
161 
removeTestUsers()162     protected void removeTestUsers() throws Exception {
163         for (int userId : listUsers()) {
164             if (userId != 0) {
165                 removeUser(userId);
166             }
167         }
168     }
169 
170     /** Returns true if the specified tests passed. Tests are run as user owner. */
runDeviceTests(String pkgName, @Nullable String testClassName)171     protected boolean runDeviceTests(String pkgName, @Nullable String testClassName)
172             throws DeviceNotAvailableException {
173         return runDeviceTests(pkgName, testClassName, null /*testMethodName*/, null /*userId*/);
174     }
175 
176     /** Returns true if the specified tests passed. Tests are run as given user. */
runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, int userId)177     protected boolean runDeviceTestsAsUser(
178             String pkgName, @Nullable String testClassName, int userId)
179             throws DeviceNotAvailableException {
180         return runDeviceTestsAsUser(pkgName, testClassName, null, userId);
181     }
182 
183     /** Returns true if the specified tests passed. Tests are run as given user. */
runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, String testMethodName, int userId)184     protected boolean runDeviceTestsAsUser(
185             String pkgName, @Nullable String testClassName, String testMethodName, int userId)
186             throws DeviceNotAvailableException {
187         return runDeviceTests(pkgName, testClassName, testMethodName, userId);
188     }
189 
runDeviceTests(String pkgName, @Nullable String testClassName, @Nullable String testMethodName, @Nullable Integer userId)190     protected boolean runDeviceTests(String pkgName, @Nullable String testClassName,
191             @Nullable String testMethodName, @Nullable Integer userId)
192             throws DeviceNotAvailableException {
193         return runDeviceTests(pkgName, testClassName, testMethodName, userId, /*params*/ null);
194     }
195 
runDeviceTests(String pkgName, @Nullable String testClassName, @Nullable String testMethodName, @Nullable Integer userId, @Nullable String params)196     protected boolean runDeviceTests(String pkgName, @Nullable String testClassName,
197             @Nullable String testMethodName, @Nullable Integer userId, @Nullable String params)
198                    throws DeviceNotAvailableException {
199         if (testClassName != null && testClassName.startsWith(".")) {
200             testClassName = pkgName + testClassName;
201         }
202         TestRunResult runResult = (userId == null && params == null)
203                 ? doRunTests(pkgName, testClassName, testMethodName)
204                 : doRunTestsAsUser(pkgName, testClassName, testMethodName,
205                         userId != null ? userId : 0, params != null ? params : "");
206         printTestResult(runResult);
207         return !runResult.hasFailedTests() && runResult.getNumTestsInState(TestStatus.PASSED) > 0;
208     }
209 
210     /** Helper method to run tests and return the listener that collected the results. */
doRunTests( String pkgName, String testClassName, String testMethodName)211     private TestRunResult doRunTests(
212             String pkgName, String testClassName,
213             String testMethodName) throws DeviceNotAvailableException {
214         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
215                 pkgName, RUNNER, getDevice().getIDevice());
216         if (testClassName != null && testMethodName != null) {
217             testRunner.setMethodName(testClassName, testMethodName);
218         } else if (testClassName != null) {
219             testRunner.setClassName(testClassName);
220         }
221 
222         CollectingTestListener listener = new CollectingTestListener();
223         assertTrue(getDevice().runInstrumentationTests(testRunner, listener));
224         return listener.getCurrentRunResults();
225     }
226 
doRunTestsAsUser(String pkgName, @Nullable String testClassName, @Nullable String testMethodName, int userId, String params)227     private TestRunResult doRunTestsAsUser(String pkgName, @Nullable String testClassName,
228             @Nullable String testMethodName, int userId, String params)
229             throws DeviceNotAvailableException {
230         // TODO: move this to RemoteAndroidTestRunner once it supports users. Should be straight
231         // forward to add a RemoteAndroidTestRunner.setUser(userId) method. Then we can merge both
232         // doRunTests* methods.
233         StringBuilder testsToRun = new StringBuilder();
234         if (testClassName != null) {
235             testsToRun.append("-e class " + testClassName);
236             if (testMethodName != null) {
237                 testsToRun.append("#" + testMethodName);
238             }
239         }
240         String command = "am instrument --user " + userId + " " + params + " -w -r "
241                 + testsToRun + " " + pkgName + "/" + RUNNER;
242         CLog.i("Running " + command);
243 
244         CollectingTestListener listener = new CollectingTestListener();
245         InstrumentationResultParser parser = new InstrumentationResultParser(pkgName, listener);
246         getDevice().executeShellCommand(command, parser);
247         return listener.getCurrentRunResults();
248     }
249 
printTestResult(TestRunResult runResult)250     private void printTestResult(TestRunResult runResult) {
251         for (Map.Entry<TestIdentifier, TestResult> testEntry :
252                 runResult.getTestResults().entrySet()) {
253             TestResult testResult = testEntry.getValue();
254             CLog.logAndDisplay(LogLevel.INFO,
255                     "Test " + testEntry.getKey() + ": " + testResult.getStatus());
256             if (testResult.getStatus() != TestStatus.PASSED) {
257                 CLog.logAndDisplay(LogLevel.WARN, testResult.getStackTrace());
258             }
259         }
260     }
261 
hasDeviceFeature(String requiredFeature)262     protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
263         if (mAvailableFeatures == null) {
264             // TODO: Move this logic to ITestDevice.
265             String command = "pm list features";
266             String commandOutput = getDevice().executeShellCommand(command);
267             CLog.i("Output for command " + command + ": " + commandOutput);
268 
269             // Extract the id of the new user.
270             mAvailableFeatures = new HashSet<>();
271             for (String feature: commandOutput.split("\\s+")) {
272                 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
273                 String[] tokens = feature.split(":");
274                 assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
275                         tokens.length > 1);
276                 assertEquals(feature, "feature", tokens[0]);
277                 mAvailableFeatures.add(tokens[1]);
278             }
279         }
280         boolean result = mAvailableFeatures.contains(requiredFeature);
281         if (!result) {
282             CLog.logAndDisplay(LogLevel.INFO, "Device doesn't have required feature "
283             + requiredFeature + ". Test won't run.");
284         }
285         return result;
286     }
287 
createUser()288     protected int createUser() throws Exception {
289         String command ="pm create-user TestUser_"+ System.currentTimeMillis();
290         CLog.logAndDisplay(LogLevel.INFO, "Starting command " + command);
291         String commandOutput = getDevice().executeShellCommand(command);
292         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
293 
294         // Extract the id of the new user.
295         String[] tokens = commandOutput.split("\\s+");
296         assertTrue(tokens.length > 0);
297         assertEquals("Success:", tokens[0]);
298         return Integer.parseInt(tokens[tokens.length-1]);
299     }
300 
createManagedProfile()301     protected int createManagedProfile() throws DeviceNotAvailableException {
302         String command =
303                 "pm create-user --profileOf 0 --managed TestProfile_" + System.currentTimeMillis();
304         CLog.logAndDisplay(LogLevel.INFO, "Starting command " + command);
305         String commandOutput = getDevice().executeShellCommand(command);
306         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
307 
308         // Extract the id of the new user.
309         String[] tokens = commandOutput.split("\\s+");
310         assertTrue(commandOutput + " expected to have format \"Success: {USER_ID}\"",
311                 tokens.length > 0);
312         assertEquals(commandOutput, "Success:", tokens[0]);
313         return Integer.parseInt(tokens[tokens.length-1]);
314     }
315 
getUserSerialNumber(int userId)316     protected int getUserSerialNumber(int userId) throws DeviceNotAvailableException{
317         // dumpsys user return lines like "UserInfo{0:Owner:13} serialNo=0"
318         String commandOutput = getDevice().executeShellCommand("dumpsys user");
319         String[] tokens = commandOutput.split("\\n");
320         for (String token : tokens) {
321             token = token.trim();
322             if (token.contains("UserInfo{" + userId + ":")) {
323                 String[] split = token.split("serialNo=");
324                 assertTrue(split.length == 2);
325                 int serialNumber = Integer.parseInt(split[1]);
326                 CLog.logAndDisplay(LogLevel.INFO, "Serial number of user " + userId + ": "
327                         + serialNumber);
328                 return serialNumber;
329             }
330         }
331         fail("Couldn't find user " + userId);
332         return -1;
333     }
334 
setProfileOwner(String componentName, int userId)335     protected boolean setProfileOwner(String componentName, int userId)
336             throws DeviceNotAvailableException {
337         String command = "dpm set-profile-owner --user " + userId + " '" + componentName + "'";
338         String commandOutput = getDevice().executeShellCommand(command);
339         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
340         return commandOutput.startsWith("Success:");
341     }
342 
setProfileOwnerOrFail(String componentName, int userId)343     protected void setProfileOwnerOrFail(String componentName, int userId)
344             throws Exception {
345         if (!setProfileOwner(componentName, userId)) {
346             removeUser(userId);
347             fail("Failed to set profile owner");
348         }
349     }
350 
setDeviceAdmin(String componentName)351     protected void setDeviceAdmin(String componentName) throws DeviceNotAvailableException {
352         String command = "dpm set-active-admin '" + componentName + "'";
353         String commandOutput = getDevice().executeShellCommand(command);
354         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
355         assertTrue(commandOutput + " expected to start with \"Success:\"",
356                 commandOutput.startsWith("Success:"));
357     }
358 
setDeviceOwner(String componentName)359     protected boolean setDeviceOwner(String componentName) throws DeviceNotAvailableException {
360         String command = "dpm set-device-owner '" + componentName + "'";
361         String commandOutput = getDevice().executeShellCommand(command);
362         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
363         return commandOutput.startsWith("Success:");
364     }
365 
getSettings(String namespace, String name, int userId)366     protected String getSettings(String namespace, String name, int userId)
367             throws DeviceNotAvailableException {
368         String command = "settings --user " + userId + " get " + namespace + " " + name;
369         String commandOutput = getDevice().executeShellCommand(command);
370         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
371         return commandOutput.replace("\n", "").replace("\r", "");
372     }
373 
putSettings(String namespace, String name, String value, int userId)374     protected void putSettings(String namespace, String name, String value, int userId)
375             throws DeviceNotAvailableException {
376         String command = "settings --user " + userId + " put " + namespace + " " + name
377                 + " " + value;
378         String commandOutput = getDevice().executeShellCommand(command);
379         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
380     }
381 }
382