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