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