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