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.compatibility.common.tradefed.build.CompatibilityBuildHelper; 20 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; 21 import com.android.ddmlib.testrunner.TestResult.TestStatus; 22 import com.android.tradefed.build.IBuildInfo; 23 import com.android.tradefed.config.Option; 24 import com.android.tradefed.device.CollectingOutputReceiver; 25 import com.android.tradefed.device.DeviceNotAvailableException; 26 import com.android.tradefed.log.LogUtil.CLog; 27 import com.android.tradefed.result.CollectingTestListener; 28 import com.android.tradefed.result.FileInputStreamSource; 29 import com.android.tradefed.result.LogDataType; 30 import com.android.tradefed.result.TestDescription; 31 import com.android.tradefed.result.TestResult; 32 import com.android.tradefed.result.TestRunResult; 33 import com.android.tradefed.testtype.DeviceTestCase; 34 import com.android.tradefed.testtype.IBuildReceiver; 35 import com.android.tradefed.util.FileUtil; 36 import com.android.tradefed.util.TarUtil; 37 38 import java.io.File; 39 import java.io.FileNotFoundException; 40 import java.io.IOException; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.Collections; 44 import java.util.HashSet; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Set; 48 import java.util.concurrent.TimeUnit; 49 50 import javax.annotation.Nullable; 51 52 /** 53 * Base class for device policy tests. It offers utility methods to run tests, set device or profile 54 * owner, etc. 55 */ 56 public class BaseDevicePolicyTest extends DeviceTestCase implements IBuildReceiver { 57 58 @Option( 59 name = "skip-device-admin-feature-check", 60 description = "Flag that allows to skip the check for android.software.device_admin " 61 + "and run the tests no matter what. This is useful for system that do not what " 62 + "to expose that feature publicly." 63 ) 64 private boolean mSkipDeviceAdminFeatureCheck = false; 65 66 private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner"; 67 68 protected static final int USER_SYSTEM = 0; // From the UserHandle class. 69 70 protected static final int USER_OWNER = 0; 71 72 private static final long TIMEOUT_USER_REMOVED_MILLIS = TimeUnit.SECONDS.toMillis(15); 73 private static final long WAIT_SAMPLE_INTERVAL_MILLIS = 200; 74 75 /** 76 * The defined timeout (in milliseconds) is used as a maximum waiting time when expecting the 77 * command output from the device. At any time, if the shell command does not output anything 78 * for a period longer than defined timeout the Tradefed run terminates. 79 */ 80 private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(20); 81 82 /** instrumentation test runner argument key used for individual test timeout */ 83 protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec"; 84 85 /** 86 * Sets timeout (in milliseconds) that will be applied to each test. In the 87 * event of a test timeout it will log the results and proceed with executing the next test. 88 */ 89 private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10); 90 91 // From the UserInfo class 92 protected static final int FLAG_PRIMARY = 0x00000001; 93 protected static final int FLAG_GUEST = 0x00000004; 94 protected static final int FLAG_EPHEMERAL = 0x00000100; 95 protected static final int FLAG_MANAGED_PROFILE = 0x00000020; 96 97 protected static interface Settings { 98 public static final String GLOBAL_NAMESPACE = "global"; 99 public static interface Global { 100 public static final String DEVICE_PROVISIONED = "device_provisioned"; 101 } 102 } 103 104 protected IBuildInfo mCtsBuild; 105 106 private String mPackageVerifier; 107 private HashSet<String> mAvailableFeatures; 108 109 /** Packages installed as part of the tests */ 110 private Set<String> mFixedPackages; 111 112 /** Whether DPM is supported. */ 113 protected boolean mHasFeature; 114 protected int mPrimaryUserId; 115 116 /** Whether multi-user is supported. */ 117 protected boolean mSupportsMultiUser; 118 119 /** Whether file-based encryption (FBE) is supported. */ 120 protected boolean mSupportsFbe; 121 122 /** Users we shouldn't delete in the tests */ 123 private ArrayList<Integer> mFixedUsers; 124 125 @Override setBuild(IBuildInfo buildInfo)126 public void setBuild(IBuildInfo buildInfo) { 127 mCtsBuild = buildInfo; 128 } 129 130 @Override setUp()131 protected void setUp() throws Exception { 132 super.setUp(); 133 assertNotNull(mCtsBuild); // ensure build has been set before test is run. 134 mHasFeature = getDevice().getApiLevel() >= 21; /* Build.VERSION_CODES.L */ 135 if (!mSkipDeviceAdminFeatureCheck) { 136 mHasFeature = mHasFeature && hasDeviceFeature("android.software.device_admin"); 137 } 138 mSupportsMultiUser = getMaxNumberOfUsersSupported() > 1; 139 mSupportsFbe = hasDeviceFeature("android.software.file_based_encryption"); 140 mFixedPackages = getDevice().getInstalledPackageNames(); 141 142 // disable the package verifier to avoid the dialog when installing an app 143 mPackageVerifier = getDevice().executeShellCommand( 144 "settings get global package_verifier_enable"); 145 getDevice().executeShellCommand("settings put global package_verifier_enable 0"); 146 147 mFixedUsers = new ArrayList<>(); 148 mPrimaryUserId = getPrimaryUser(); 149 mFixedUsers.add(mPrimaryUserId); 150 if (mPrimaryUserId != USER_SYSTEM) { 151 mFixedUsers.add(USER_SYSTEM); 152 } 153 switchUser(mPrimaryUserId); 154 removeOwners(); 155 removeTestUsers(); 156 // Unlock keyguard before test 157 wakeupAndDismissKeyguard(); 158 // Go to home. 159 executeShellCommand("input keyevent KEYCODE_HOME"); 160 } 161 162 @Override tearDown()163 protected void tearDown() throws Exception { 164 // reset the package verifier setting to its original value 165 getDevice().executeShellCommand("settings put global package_verifier_enable " 166 + mPackageVerifier); 167 removeOwners(); 168 removeTestUsers(); 169 removeTestPackages(); 170 super.tearDown(); 171 } 172 installAppAsUser(String appFileName, int userId)173 protected void installAppAsUser(String appFileName, int userId) throws FileNotFoundException, 174 DeviceNotAvailableException { 175 installAppAsUser(appFileName, true, userId); 176 } 177 installAppAsUser(String appFileName, boolean grantPermissions, int userId)178 protected void installAppAsUser(String appFileName, boolean grantPermissions, int userId) 179 throws FileNotFoundException, DeviceNotAvailableException { 180 CLog.d("Installing app " + appFileName + " for user " + userId); 181 CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild); 182 String result = getDevice().installPackageForUser( 183 buildHelper.getTestFile(appFileName), true, grantPermissions, userId, "-t"); 184 assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result, 185 result); 186 } 187 forceStopPackageForUser(String packageName, int userId)188 protected void forceStopPackageForUser(String packageName, int userId) throws Exception { 189 // TODO Move this logic to ITestDevice 190 executeShellCommand("am force-stop --user " + userId + " " + packageName); 191 } 192 executeShellCommand(final String command)193 protected void executeShellCommand(final String command) throws Exception { 194 CLog.d("Starting command " + command); 195 String commandOutput = getDevice().executeShellCommand(command); 196 CLog.d("Output for command " + command + ": " + commandOutput); 197 } 198 199 /** Initializes the user with the given id. This is required so that apps can run on it. */ startUser(int userId)200 protected void startUser(int userId) throws Exception { 201 getDevice().startUser(userId); 202 } 203 204 /** 205 * Starts switching to the user with the given ID. 206 * 207 * <p>This is not blocking. Some operations will be flaky if called immediately afterwards, such 208 * as {@link #wakeupAndDismissKeyguard()}. Call {@link #waitForBroadcastIdle()} between this 209 * method and those operations to ensure that switching the user has finished. 210 */ switchUser(int userId)211 protected void switchUser(int userId) throws Exception { 212 // TODO Move this logic to ITestDevice 213 executeShellCommand("am switch-user " + userId); 214 } 215 getMaxNumberOfUsersSupported()216 protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException { 217 return getDevice().getMaxNumberOfUsersSupported(); 218 } 219 getMaxNumberOfRunningUsersSupported()220 protected int getMaxNumberOfRunningUsersSupported() throws DeviceNotAvailableException { 221 return getDevice().getMaxNumberOfRunningUsersSupported(); 222 } 223 getUserFlags(int userId)224 protected int getUserFlags(int userId) throws DeviceNotAvailableException { 225 String command = "pm list users"; 226 String commandOutput = getDevice().executeShellCommand(command); 227 CLog.i("Output for command " + command + ": " + commandOutput); 228 229 String[] lines = commandOutput.split("\\r?\\n"); 230 assertTrue(commandOutput + " should contain at least one line", lines.length >= 1); 231 for (int i = 1; i < lines.length; i++) { 232 // Individual user is printed out like this: 233 // \tUserInfo{$id$:$name$:$Integer.toHexString(flags)$} [running] 234 String[] tokens = lines[i].split("\\{|\\}|:"); 235 assertTrue(lines[i] + " doesn't contain 4 or 5 tokens", 236 tokens.length == 4 || tokens.length == 5); 237 // If the user IDs match, return the flags. 238 if (Integer.parseInt(tokens[1]) == userId) { 239 return Integer.parseInt(tokens[3], 16); 240 } 241 } 242 fail("User not found"); 243 return 0; 244 } 245 listUsers()246 protected ArrayList<Integer> listUsers() throws DeviceNotAvailableException { 247 return getDevice().listUsers(); 248 } 249 listRunningUsers()250 protected ArrayList<Integer> listRunningUsers() throws DeviceNotAvailableException { 251 ArrayList<Integer> runningUsers = new ArrayList<>(); 252 for (int userId : listUsers()) { 253 if (getDevice().isUserRunning(userId)) { 254 runningUsers.add(userId); 255 } 256 } 257 return runningUsers; 258 } 259 getFirstManagedProfileUserId()260 protected int getFirstManagedProfileUserId() throws DeviceNotAvailableException { 261 for (int userId : listUsers()) { 262 if ((getUserFlags(userId) & FLAG_MANAGED_PROFILE) != 0) { 263 return userId; 264 } 265 } 266 fail("Managed profile not found"); 267 return 0; 268 } 269 stopUserAsync(int userId)270 private void stopUserAsync(int userId) throws Exception { 271 String stopUserCommand = "am stop-user -f " + userId; 272 CLog.d("starting command \"" + stopUserCommand); 273 CLog.d("Output for command " + stopUserCommand + ": " 274 + getDevice().executeShellCommand(stopUserCommand)); 275 } 276 stopUser(int userId)277 protected void stopUser(int userId) throws Exception { 278 String stopUserCommand = "am stop-user -w -f " + userId; 279 CLog.d("starting command \"" + stopUserCommand + "\" and waiting."); 280 CLog.d("Output for command " + stopUserCommand + ": " 281 + getDevice().executeShellCommand(stopUserCommand)); 282 } 283 waitForBroadcastIdle()284 protected void waitForBroadcastIdle() throws DeviceNotAvailableException, IOException { 285 CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 286 try { 287 // we allow 8min for the command to complete and 4min for the command to start to 288 // output something 289 getDevice().executeShellCommand( 290 "am wait-for-broadcast-idle", receiver, 8, 4, TimeUnit.MINUTES, 0); 291 } finally { 292 String output = receiver.getOutput(); 293 CLog.d("Output from 'am wait-for-broadcast-idle': %s", output); 294 if (!output.contains("All broadcast queues are idle!")) { 295 // Gather the system_server dump data for investigation. 296 File heapDump = getDevice().dumpHeap("system_server", "/data/local/tmp/dump.hprof"); 297 if (heapDump != null) { 298 // If file is too too big, tar if with TarUtil. 299 String pid = getDevice().getProcessPid("system_server"); 300 // gzip the file it's quite big 301 File heapDumpGz = TarUtil.gzip(heapDump); 302 try (FileInputStreamSource source = new FileInputStreamSource(heapDumpGz)) { 303 addTestLog( 304 String.format("system_server_dump.%s.%s.hprof", 305 pid, getDevice().getDeviceDate()), 306 LogDataType.GZIP, source); 307 } finally { 308 FileUtil.deleteFile(heapDump); 309 } 310 } else { 311 CLog.e("Failed to capture the dumpheap from system_server"); 312 } 313 // the call most likely failed we should fail the test 314 fail("'am wait-for-broadcase-idle' did not complete."); 315 // TODO: consider adding a reboot or recovery before failing if necessary 316 } 317 } 318 } 319 removeUser(int userId)320 protected void removeUser(int userId) throws Exception { 321 if (listUsers().contains(userId) && userId != USER_SYSTEM) { 322 // Don't log output, as tests sometimes set no debug user restriction, which 323 // causes this to fail, we should still continue and remove the user. 324 String stopUserCommand = "am stop-user -w -f " + userId; 325 CLog.d("stopping and removing user " + userId); 326 getDevice().executeShellCommand(stopUserCommand); 327 // Ephemeral users may have already been removed after being stopped. 328 if (listUsers().contains(userId)) { 329 assertTrue("Couldn't remove user", getDevice().removeUser(userId)); 330 } 331 } 332 } 333 removeTestUsers()334 protected void removeTestUsers() throws Exception { 335 List<Integer> usersCreatedByTests = getUsersCreatedByTests(); 336 337 // The time spent on stopUser is depend on how busy the broadcast queue is. 338 // To optimize the time to remove multiple test users, we mark all users as 339 // stopping first, so no more broadcasts will be sent to these users, which make the queue 340 // less busy. 341 for (int userId : usersCreatedByTests) { 342 stopUserAsync(userId); 343 } 344 for (int userId : usersCreatedByTests) { 345 removeUser(userId); 346 } 347 } 348 349 /** 350 * Returns the users that have been created since running this class' setUp() method. 351 */ getUsersCreatedByTests()352 protected List<Integer> getUsersCreatedByTests() throws Exception { 353 List<Integer> result = listUsers(); 354 result.removeAll(mFixedUsers); 355 return result; 356 } 357 358 /** Removes any packages that were installed during the test. */ removeTestPackages()359 protected void removeTestPackages() throws Exception { 360 for (String packageName : getDevice().getUninstallablePackageNames()) { 361 if (mFixedPackages.contains(packageName)) { 362 continue; 363 } 364 CLog.w("removing leftover package: " + packageName); 365 getDevice().uninstallPackage(packageName); 366 } 367 } 368 runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, int userId)369 protected void runDeviceTestsAsUser( 370 String pkgName, @Nullable String testClassName, int userId) 371 throws DeviceNotAvailableException { 372 runDeviceTestsAsUser(pkgName, testClassName, null /*testMethodName*/, userId); 373 } 374 runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, String testMethodName, int userId)375 protected void runDeviceTestsAsUser( 376 String pkgName, @Nullable String testClassName, String testMethodName, int userId) 377 throws DeviceNotAvailableException { 378 Map<String, String> params = Collections.emptyMap(); 379 runDeviceTestsAsUser(pkgName, testClassName, testMethodName, userId, params); 380 } 381 runDeviceTests( String pkgName, @Nullable String testClassName, String testMethodName)382 protected void runDeviceTests( 383 String pkgName, @Nullable String testClassName, String testMethodName) 384 throws DeviceNotAvailableException { 385 runDeviceTestsAsUser(pkgName, testClassName, testMethodName, mPrimaryUserId); 386 } 387 runDeviceTestsAsUser( String pkgName, @Nullable String testClassName, @Nullable String testMethodName, int userId, Map<String, String> params)388 protected void runDeviceTestsAsUser( 389 String pkgName, @Nullable String testClassName, 390 @Nullable String testMethodName, int userId, 391 Map<String, String> params) throws DeviceNotAvailableException { 392 if (testClassName != null && testClassName.startsWith(".")) { 393 testClassName = pkgName + testClassName; 394 } 395 396 RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner( 397 pkgName, RUNNER, getDevice().getIDevice()); 398 testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 399 testRunner.addInstrumentationArg( 400 TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS)); 401 if (testClassName != null && testMethodName != null) { 402 testRunner.setMethodName(testClassName, testMethodName); 403 } else if (testClassName != null) { 404 testRunner.setClassName(testClassName); 405 } 406 407 for (Map.Entry<String, String> param : params.entrySet()) { 408 testRunner.addInstrumentationArg(param.getKey(), param.getValue()); 409 } 410 411 CollectingTestListener listener = new CollectingTestListener(); 412 assertTrue(getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener)); 413 414 final TestRunResult result = listener.getCurrentRunResults(); 415 if (result.isRunFailure()) { 416 throw new AssertionError("Failed to successfully run device tests for " 417 + result.getName() + ": " + result.getRunFailureMessage()); 418 } 419 if (result.getNumTests() == 0) { 420 throw new AssertionError("No tests were run on the device"); 421 } 422 423 if (result.hasFailedTests()) { 424 // build a meaningful error message 425 StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n"); 426 for (Map.Entry<TestDescription, TestResult> resultEntry : 427 result.getTestResults().entrySet()) { 428 if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) { 429 errorBuilder.append(resultEntry.getKey().toString()); 430 errorBuilder.append(":\n"); 431 errorBuilder.append(resultEntry.getValue().getStackTrace()); 432 } 433 } 434 throw new AssertionError(errorBuilder.toString()); 435 } 436 } 437 438 /** Reboots the device and block until the boot complete flag is set. */ rebootAndWaitUntilReady()439 protected void rebootAndWaitUntilReady() throws DeviceNotAvailableException { 440 getDevice().executeShellCommand("reboot"); 441 assertTrue("Device failed to boot", getDevice().waitForBootComplete(120000)); 442 } 443 444 /** Returns true if the system supports the split between system and primary user. */ hasUserSplit()445 protected boolean hasUserSplit() throws DeviceNotAvailableException { 446 return getBooleanSystemProperty("ro.fw.system_user_split", false); 447 } 448 449 /** Returns a boolean value of the system property with the specified key. */ getBooleanSystemProperty(String key, boolean defaultValue)450 protected boolean getBooleanSystemProperty(String key, boolean defaultValue) 451 throws DeviceNotAvailableException { 452 final String[] positiveValues = {"1", "y", "yes", "true", "on"}; 453 final String[] negativeValues = {"0", "n", "no", "false", "off"}; 454 String propertyValue = getDevice().getProperty(key); 455 if (propertyValue == null || propertyValue.isEmpty()) { 456 return defaultValue; 457 } 458 if (Arrays.asList(positiveValues).contains(propertyValue)) { 459 return true; 460 } 461 if (Arrays.asList(negativeValues).contains(propertyValue)) { 462 return false; 463 } 464 fail("Unexpected value of boolean system property '" + key + "': " + propertyValue); 465 return false; 466 } 467 468 /** Checks whether it is possible to create the desired number of users. */ canCreateAdditionalUsers(int numberOfUsers)469 protected boolean canCreateAdditionalUsers(int numberOfUsers) 470 throws DeviceNotAvailableException { 471 return listUsers().size() + numberOfUsers <= getMaxNumberOfUsersSupported(); 472 } 473 474 /** Checks whether it is possible to start the desired number of users. */ canStartAdditionalUsers(int numberOfUsers)475 protected boolean canStartAdditionalUsers(int numberOfUsers) 476 throws DeviceNotAvailableException { 477 return listRunningUsers().size() + numberOfUsers <= getMaxNumberOfRunningUsersSupported(); 478 } 479 hasDeviceFeature(String requiredFeature)480 protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException { 481 if (mAvailableFeatures == null) { 482 // TODO: Move this logic to ITestDevice. 483 String command = "pm list features"; 484 String commandOutput = getDevice().executeShellCommand(command); 485 CLog.i("Output for command " + command + ": " + commandOutput); 486 487 // Extract the id of the new user. 488 mAvailableFeatures = new HashSet<>(); 489 for (String feature: commandOutput.split("\\s+")) { 490 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}". 491 String[] tokens = feature.split(":"); 492 assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}", 493 tokens.length > 1); 494 assertEquals(feature, "feature", tokens[0]); 495 mAvailableFeatures.add(tokens[1]); 496 } 497 } 498 boolean result = mAvailableFeatures.contains(requiredFeature); 499 if (!result) { 500 CLog.d("Device doesn't have required feature " 501 + requiredFeature + ". Test won't run."); 502 } 503 return result; 504 } 505 createUser()506 protected int createUser() throws Exception { 507 int userId = createUser(0); 508 // TODO remove this and audit tests so they start users as necessary 509 startUser(userId); 510 return userId; 511 } 512 createUser(int flags)513 protected int createUser(int flags) throws Exception { 514 boolean guest = FLAG_GUEST == (flags & FLAG_GUEST); 515 boolean ephemeral = FLAG_EPHEMERAL == (flags & FLAG_EPHEMERAL); 516 // TODO Use ITestDevice.createUser() when guest and ephemeral is available 517 String command ="pm create-user " + (guest ? "--guest " : "") 518 + (ephemeral ? "--ephemeral " : "") + "TestUser_" + System.currentTimeMillis(); 519 CLog.d("Starting command " + command); 520 String commandOutput = getDevice().executeShellCommand(command); 521 CLog.d("Output for command " + command + ": " + commandOutput); 522 523 // Extract the id of the new user. 524 String[] tokens = commandOutput.split("\\s+"); 525 assertTrue(tokens.length > 0); 526 assertEquals("Success:", tokens[0]); 527 return Integer.parseInt(tokens[tokens.length-1]); 528 } 529 createManagedProfile(int parentUserId)530 protected int createManagedProfile(int parentUserId) throws DeviceNotAvailableException { 531 String commandOutput = getCreateManagedProfileCommandOutput(parentUserId); 532 return getUserIdFromCreateUserCommandOutput(commandOutput); 533 } 534 assertCannotCreateManagedProfile(int parentUserId)535 protected void assertCannotCreateManagedProfile(int parentUserId) 536 throws Exception { 537 String commandOutput = getCreateManagedProfileCommandOutput(parentUserId); 538 if (commandOutput.startsWith("Error")) { 539 return; 540 } 541 int userId = getUserIdFromCreateUserCommandOutput(commandOutput); 542 removeUser(userId); 543 fail("Expected not to be able to create a managed profile. Output was: " + commandOutput); 544 } 545 getUserIdFromCreateUserCommandOutput(String commandOutput)546 private int getUserIdFromCreateUserCommandOutput(String commandOutput) { 547 // Extract the id of the new user. 548 String[] tokens = commandOutput.split("\\s+"); 549 assertTrue(commandOutput + " expected to have format \"Success: {USER_ID}\"", 550 tokens.length > 0); 551 assertEquals(commandOutput, "Success:", tokens[0]); 552 return Integer.parseInt(tokens[tokens.length-1]); 553 } 554 getCreateManagedProfileCommandOutput(int parentUserId)555 private String getCreateManagedProfileCommandOutput(int parentUserId) 556 throws DeviceNotAvailableException { 557 String command = "pm create-user --profileOf " + parentUserId + " --managed " 558 + "TestProfile_" + System.currentTimeMillis(); 559 CLog.d("Starting command " + command); 560 String commandOutput = getDevice().executeShellCommand(command); 561 CLog.d("Output for command " + command + ": " + commandOutput); 562 return commandOutput; 563 } 564 getPrimaryUser()565 protected int getPrimaryUser() throws DeviceNotAvailableException { 566 return getDevice().getPrimaryUserId(); 567 } 568 getUserSerialNumber(int userId)569 protected int getUserSerialNumber(int userId) throws DeviceNotAvailableException{ 570 // TODO: Move this logic to ITestDevice. 571 // dumpsys user return lines like "UserInfo{0:Owner:13} serialNo=0" 572 String commandOutput = getDevice().executeShellCommand("dumpsys user"); 573 String[] tokens = commandOutput.split("\\n"); 574 for (String token : tokens) { 575 token = token.trim(); 576 if (token.contains("UserInfo{" + userId + ":")) { 577 String[] split = token.split("serialNo="); 578 assertTrue(split.length == 2); 579 int serialNumber = Integer.parseInt(split[1]); 580 CLog.d("Serial number of user " + userId + ": " 581 + serialNumber); 582 return serialNumber; 583 } 584 } 585 fail("Couldn't find user " + userId); 586 return -1; 587 } 588 setProfileOwner(String componentName, int userId, boolean expectFailure)589 protected boolean setProfileOwner(String componentName, int userId, boolean expectFailure) 590 throws DeviceNotAvailableException { 591 String command = "dpm set-profile-owner --user " + userId + " '" + componentName + "'"; 592 String commandOutput = getDevice().executeShellCommand(command); 593 boolean success = commandOutput.startsWith("Success:"); 594 // If we succeeded always log, if we are expecting failure don't log failures 595 // as call stacks for passing tests confuse the logs. 596 if (success || !expectFailure) { 597 CLog.d("Output for command " + command + ": " + commandOutput); 598 } else { 599 CLog.d("Command Failed " + command); 600 } 601 return success; 602 } 603 setProfileOwnerOrFail(String componentName, int userId)604 protected void setProfileOwnerOrFail(String componentName, int userId) 605 throws Exception { 606 if (!setProfileOwner(componentName, userId, /*expectFailure*/ false)) { 607 if (userId != 0) { // don't remove system user. 608 removeUser(userId); 609 } 610 fail("Failed to set profile owner"); 611 } 612 } 613 setProfileOwnerExpectingFailure(String componentName, int userId)614 protected void setProfileOwnerExpectingFailure(String componentName, int userId) 615 throws Exception { 616 if (setProfileOwner(componentName, userId, /* expectFailure =*/ true)) { 617 if (userId != 0) { // don't remove system user. 618 removeUser(userId); 619 } 620 fail("Setting profile owner should have failed."); 621 } 622 } 623 setDeviceAdminInner(String componentName, int userId)624 private String setDeviceAdminInner(String componentName, int userId) 625 throws DeviceNotAvailableException { 626 String command = "dpm set-active-admin --user " + userId + " '" + componentName + "'"; 627 String commandOutput = getDevice().executeShellCommand(command); 628 return commandOutput; 629 } 630 setDeviceAdmin(String componentName, int userId)631 protected void setDeviceAdmin(String componentName, int userId) 632 throws DeviceNotAvailableException { 633 String commandOutput = setDeviceAdminInner(componentName, userId); 634 CLog.d("Output for command " + commandOutput 635 + ": " + commandOutput); 636 assertTrue(commandOutput + " expected to start with \"Success:\"", 637 commandOutput.startsWith("Success:")); 638 } 639 setDeviceAdminExpectingFailure(String componentName, int userId, String errorMessage)640 protected void setDeviceAdminExpectingFailure(String componentName, int userId, 641 String errorMessage) throws DeviceNotAvailableException { 642 String commandOutput = setDeviceAdminInner(componentName, userId); 643 if (!commandOutput.contains(errorMessage)) { 644 fail(commandOutput + " expected to contain \"" + errorMessage + "\""); 645 } 646 } 647 setDeviceOwner(String componentName, int userId, boolean expectFailure)648 protected boolean setDeviceOwner(String componentName, int userId, boolean expectFailure) 649 throws DeviceNotAvailableException { 650 String command = "dpm set-device-owner --user " + userId + " '" + componentName + "'"; 651 String commandOutput = getDevice().executeShellCommand(command); 652 boolean success = commandOutput.startsWith("Success:"); 653 // If we succeeded always log, if we are expecting failure don't log failures 654 // as call stacks for passing tests confuse the logs. 655 if (success || !expectFailure) { 656 CLog.d("Output for command " + command + ": " + commandOutput); 657 } else { 658 CLog.d("Command Failed " + command); 659 } 660 return success; 661 } 662 setDeviceOwnerOrFail(String componentName, int userId)663 protected void setDeviceOwnerOrFail(String componentName, int userId) 664 throws Exception { 665 assertTrue(setDeviceOwner(componentName, userId, /* expectFailure =*/ false)); 666 } 667 setDeviceOwnerExpectingFailure(String componentName, int userId)668 protected void setDeviceOwnerExpectingFailure(String componentName, int userId) 669 throws Exception { 670 assertFalse(setDeviceOwner(componentName, userId, /* expectFailure =*/ true)); 671 } 672 getSettings(String namespace, String name, int userId)673 protected String getSettings(String namespace, String name, int userId) 674 throws DeviceNotAvailableException { 675 String command = "settings --user " + userId + " get " + namespace + " " + name; 676 String commandOutput = getDevice().executeShellCommand(command); 677 CLog.d("Output for command " + command + ": " + commandOutput); 678 return commandOutput.replace("\n", "").replace("\r", ""); 679 } 680 putSettings(String namespace, String name, String value, int userId)681 protected void putSettings(String namespace, String name, String value, int userId) 682 throws DeviceNotAvailableException { 683 String command = "settings --user " + userId + " put " + namespace + " " + name 684 + " " + value; 685 String commandOutput = getDevice().executeShellCommand(command); 686 CLog.d("Output for command " + command + ": " + commandOutput); 687 } 688 removeAdmin(String componentName, int userId)689 protected boolean removeAdmin(String componentName, int userId) 690 throws DeviceNotAvailableException { 691 String command = "dpm remove-active-admin --user " + userId + " '" + componentName + "'"; 692 String commandOutput = getDevice().executeShellCommand(command); 693 CLog.d("Output for command " + command + ": " + commandOutput); 694 return commandOutput.startsWith("Success:"); 695 } 696 697 // Tries to remove and profile or device owners it finds. removeOwners()698 protected void removeOwners() throws DeviceNotAvailableException { 699 String command = "dumpsys device_policy"; 700 String commandOutput = getDevice().executeShellCommand(command); 701 String[] lines = commandOutput.split("\\r?\\n"); 702 for (int i = 0; i < lines.length; ++i) { 703 String line = lines[i].trim(); 704 if (line.contains("Profile Owner")) { 705 // Line is "Profile owner (User <id>): 706 String[] tokens = line.split("\\(|\\)| "); 707 int userId = Integer.parseInt(tokens[4]); 708 i++; 709 line = lines[i].trim(); 710 // Line is admin=ComponentInfo{<component>} 711 tokens = line.split("\\{|\\}"); 712 String componentName = tokens[1]; 713 CLog.w("Cleaning up profile owner " + userId + " " + componentName); 714 removeAdmin(componentName, userId); 715 } else if (line.contains("Device Owner:")) { 716 i++; 717 line = lines[i].trim(); 718 // Line is admin=ComponentInfo{<component>} 719 String[] tokens = line.split("\\{|\\}"); 720 String componentName = tokens[1]; 721 // Skip to user id line. 722 i += 3; 723 line = lines[i].trim(); 724 // Line is User ID: <N> 725 tokens = line.split(":"); 726 int userId = Integer.parseInt(tokens[1].trim()); 727 CLog.w("Cleaning up device owner " + userId + " " + componentName); 728 removeAdmin(componentName, userId); 729 } 730 } 731 } 732 733 /** 734 * Runs pm enable command to enable a package or component. Returns the command result. 735 */ enableComponentOrPackage(int userId, String packageOrComponent)736 protected String enableComponentOrPackage(int userId, String packageOrComponent) 737 throws DeviceNotAvailableException { 738 String command = "pm enable --user " + userId + " " + packageOrComponent; 739 String result = getDevice().executeShellCommand(command); 740 CLog.d("Output for command " + command + ": " + result); 741 return result; 742 } 743 744 /** 745 * Runs pm disable command to disable a package or component. Returns the command result. 746 */ disableComponentOrPackage(int userId, String packageOrComponent)747 protected String disableComponentOrPackage(int userId, String packageOrComponent) 748 throws DeviceNotAvailableException { 749 String command = "pm disable --user " + userId + " " + packageOrComponent; 750 String result = getDevice().executeShellCommand(command); 751 CLog.d("Output for command " + command + ": " + result); 752 return result; 753 } 754 755 protected interface SuccessCondition { check()756 boolean check() throws Exception; 757 } 758 assertUserGetsRemoved(int userId)759 protected void assertUserGetsRemoved(int userId) throws Exception { 760 tryWaitForSuccess(() -> !listUsers().contains(userId), 761 "The user " + userId + " has not been removed", 762 TIMEOUT_USER_REMOVED_MILLIS 763 ); 764 } 765 tryWaitForSuccess(SuccessCondition successCondition, String failureMessage, long timeoutMillis)766 protected void tryWaitForSuccess(SuccessCondition successCondition, String failureMessage, 767 long timeoutMillis) throws Exception { 768 long epoch = System.currentTimeMillis(); 769 while (System.currentTimeMillis() - epoch <= timeoutMillis) { 770 Thread.sleep(WAIT_SAMPLE_INTERVAL_MILLIS); 771 if (successCondition.check()) { 772 return; 773 } 774 } 775 fail(failureMessage); 776 } 777 778 /** 779 * Sets a user restriction via SetPolicyActivity. 780 * <p>IMPORTANT: The package that contains SetPolicyActivity must have been installed prior to 781 * calling this method. 782 * @param key user restriction key 783 * @param value true if we should set the restriction, false if we should clear it 784 * @param userId userId to set/clear the user restriction on 785 * @param packageName package where SetPolicyActivity is installed 786 * @return The output of the command 787 * @throws DeviceNotAvailableException 788 */ changeUserRestriction(String key, boolean value, int userId, String packageName)789 protected String changeUserRestriction(String key, boolean value, int userId, 790 String packageName) throws DeviceNotAvailableException { 791 return changePolicy(getUserRestrictionCommand(value), 792 " --es extra-restriction-key " + key, userId, packageName); 793 } 794 795 /** 796 * Same as {@link #changeUserRestriction(String, boolean, int, String)} but asserts that it 797 * succeeds. 798 */ changeUserRestrictionOrFail(String key, boolean value, int userId, String packageName)799 protected void changeUserRestrictionOrFail(String key, boolean value, int userId, 800 String packageName) throws DeviceNotAvailableException { 801 changePolicyOrFail(getUserRestrictionCommand(value), " --es extra-restriction-key " + key, 802 userId, packageName); 803 } 804 805 /** 806 * Sets some policy via SetPolicyActivity. 807 * <p>IMPORTANT: The package that contains SetPolicyActivity must have been installed prior to 808 * calling this method. 809 * @param command command to pass to SetPolicyActivity 810 * @param extras extras to pass to SetPolicyActivity 811 * @param userId the userId where we invoke SetPolicyActivity 812 * @param packageName where SetPolicyActivity is installed 813 * @return The output of the command 814 * @throws DeviceNotAvailableException 815 */ changePolicy(String command, String extras, int userId, String packageName)816 protected String changePolicy(String command, String extras, int userId, String packageName) 817 throws DeviceNotAvailableException { 818 String adbCommand = "am start -W --user " + userId 819 + " -c android.intent.category.DEFAULT " 820 + " --es extra-command " + command 821 + " " + extras 822 + " " + packageName + "/.SetPolicyActivity"; 823 String commandOutput = getDevice().executeShellCommand(adbCommand); 824 CLog.d("Output for command " + adbCommand + ": " + commandOutput); 825 return commandOutput; 826 } 827 828 /** 829 * Same as {@link #changePolicy(String, String, int, String)} but asserts that it succeeds. 830 */ changePolicyOrFail(String command, String extras, int userId, String packageName)831 protected void changePolicyOrFail(String command, String extras, int userId, 832 String packageName) throws DeviceNotAvailableException { 833 String commandOutput = changePolicy(command, extras, userId, packageName); 834 assertTrue("Command was expected to succeed " + commandOutput, 835 commandOutput.contains("Status: ok")); 836 } 837 getUserRestrictionCommand(boolean setRestriction)838 private String getUserRestrictionCommand(boolean setRestriction) { 839 if (setRestriction) { 840 return "add-restriction"; 841 } 842 return "clear-restriction"; 843 } 844 845 /** 846 * Set lockscreen password / work challenge for the given user, null or "" means clear 847 */ changeUserCredential(String newCredential, String oldCredential, int userId)848 protected void changeUserCredential(String newCredential, String oldCredential, int userId) 849 throws DeviceNotAvailableException { 850 final String oldCredentialArgument = (oldCredential == null || oldCredential.isEmpty()) ? "" 851 : ("--old " + oldCredential); 852 if (newCredential != null && !newCredential.isEmpty()) { 853 String commandOutput = getDevice().executeShellCommand(String.format( 854 "cmd lock_settings set-password --user %d %s %s", userId, oldCredentialArgument, 855 newCredential)); 856 if (!commandOutput.startsWith("Password set to")) { 857 fail("Failed to set user credential: " + commandOutput); 858 } 859 } else { 860 String commandOutput = getDevice().executeShellCommand(String.format( 861 "cmd lock_settings clear --user %d %s", userId, oldCredentialArgument)); 862 if (!commandOutput.startsWith("Lock credential cleared")) { 863 fail("Failed to clear user credential: " + commandOutput); 864 } 865 } 866 } 867 868 /** 869 * Verifies the lock credential for the given user, which unlocks the user. 870 * 871 * @param credential The credential to verify. 872 * @param userId The id of the user. 873 */ verifyUserCredential(String credential, int userId)874 protected void verifyUserCredential(String credential, int userId) 875 throws DeviceNotAvailableException { 876 final String credentialArgument = (credential == null || credential.isEmpty()) 877 ? "" : ("--old " + credential); 878 String commandOutput = getDevice().executeShellCommand(String.format( 879 "cmd lock_settings verify --user %d %s", userId, credentialArgument)); 880 if (!commandOutput.startsWith("Lock credential verified")) { 881 fail("Failed to verify user credential: " + commandOutput); 882 } 883 } 884 wakeupAndDismissKeyguard()885 protected void wakeupAndDismissKeyguard() throws Exception { 886 executeShellCommand("input keyevent KEYCODE_WAKEUP"); 887 executeShellCommand("wm dismiss-keyguard"); 888 } 889 startActivityAsUser(int userId, String packageName, String activityName)890 protected void startActivityAsUser(int userId, String packageName, String activityName) 891 throws Exception { 892 wakeupAndDismissKeyguard(); 893 String command = "am start -W --user " + userId + " " + packageName + "/" + activityName; 894 getDevice().executeShellCommand(command); 895 } 896 getDefaultLauncher()897 protected String getDefaultLauncher() throws Exception { 898 final String PREFIX = "Launcher: ComponentInfo{"; 899 final String POSTFIX = "}"; 900 final String commandOutput = 901 getDevice().executeShellCommand("cmd shortcut get-default-launcher"); 902 if (commandOutput == null) { 903 return null; 904 } 905 String[] lines = commandOutput.split("\\r?\\n"); 906 for (String line : lines) { 907 if (line.startsWith(PREFIX) && line.endsWith(POSTFIX)) { 908 return line.substring(PREFIX.length(), line.length() - POSTFIX.length()); 909 } 910 } 911 throw new Exception("Default launcher not found"); 912 } 913 } 914