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