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