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