1 /* 2 * Copyright (C) 2020 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 android.car.cts; 18 19 import static com.google.common.truth.Truth.assertWithMessage; 20 21 import static org.junit.Assert.fail; 22 import static org.junit.Assume.assumeTrue; 23 24 import android.service.pm.PackageProto; 25 import android.service.pm.PackageProto.UserPermissionsProto; 26 import android.service.pm.PackageServiceDumpProto; 27 28 import com.android.compatibility.common.util.CommonTestUtils; 29 import com.android.compatibility.common.util.CommonTestUtils.BooleanSupplierWithThrow; 30 import com.android.tradefed.device.CollectingByteOutputReceiver; 31 import com.android.tradefed.device.DeviceNotAvailableException; 32 import com.android.tradefed.device.ITestDevice; 33 import com.android.tradefed.log.LogUtil.CLog; 34 import com.android.tradefed.testtype.ITestInformationReceiver; 35 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 36 37 import org.junit.After; 38 import org.junit.AssumptionViolatedException; 39 import org.junit.Before; 40 import org.junit.Rule; 41 import org.junit.rules.TestRule; 42 import org.junit.runner.Description; 43 import org.junit.runners.model.Statement; 44 45 import java.util.ArrayList; 46 import java.util.HashMap; 47 import java.util.HashSet; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.function.Function; 51 import java.util.regex.Matcher; 52 import java.util.regex.Pattern; 53 import java.util.stream.Collectors; 54 55 /** 56 * Base class for all test cases. 57 */ 58 // NOTE: must be public because of @Rules 59 public abstract class CarHostJUnit4TestCase extends BaseHostJUnit4Test { 60 61 private static final int SYSTEM_USER_ID = 0; 62 63 private static final int DEFAULT_TIMEOUT_SEC = 20; 64 protected static final int SYSTEM_RESTART_TIMEOUT_SEC = 120; 65 66 private static final Pattern CREATE_USER_OUTPUT_PATTERN = Pattern.compile("id=(\\d+)"); 67 68 private static final String USER_PREFIX = "CtsCarHostTestCases"; 69 70 /** 71 * User pattern in the output of "cmd user list --all -v" 72 * TEXT id=<id> TEXT name=<name>, TEX flags=<flags> TEXT 73 * group 1: id group 2: name group 3: flags group 4: other state(like "(running)") 74 */ 75 private static final Pattern USER_PATTERN = Pattern.compile( 76 ".*id=(\\d+).*name=([^\\s,]+).*flags=(\\S+)(.*)"); 77 78 private static final int USER_PATTERN_GROUP_ID = 1; 79 private static final int USER_PATTERN_GROUP_NAME = 2; 80 private static final int USER_PATTERN_GROUP_FLAGS = 3; 81 private static final int USER_PATTERN_GROUP_OTHER_STATE = 4; 82 83 /** 84 * User's package permission pattern string format in the output of "dumpsys package PKG_NAME" 85 */ 86 protected static final String APP_APK = "CtsCarApp.apk"; 87 protected static final String APP_PKG = "android.car.cts.app"; 88 89 @Rule 90 public final RequiredFeatureRule mHasAutomotiveRule = new RequiredFeatureRule(this, 91 "android.hardware.type.automotive"); 92 93 private final HashSet<Integer> mUsersToBeRemoved = new HashSet<>(); 94 95 private int mInitialUserId; 96 private Integer mInitialMaximumNumberOfUsers; 97 98 // It is possible that during test initial user is deleted and it is not possible to switch 99 // to the initial User. This boolean controls if test should switch to initial user on clean up. 100 private boolean mSwitchToInitialUser = true; 101 102 /** 103 * Saves multi-user state so it can be restored after the test. 104 */ 105 @Before saveUserState()106 public void saveUserState() throws Exception { 107 removeUsers(USER_PREFIX); 108 109 mInitialUserId = getCurrentUserId(); 110 } 111 112 /** 113 * Restores multi-user state from before the test. 114 */ 115 @After restoreUsersState()116 public void restoreUsersState() throws Exception { 117 int currentUserId = getCurrentUserId(); 118 CLog.d("restoreUsersState(): initial user: %d, current user: %d, created users: %s " 119 + "max number of users: %d", 120 mInitialUserId, currentUserId, mUsersToBeRemoved, mInitialMaximumNumberOfUsers); 121 if (currentUserId != mInitialUserId && mSwitchToInitialUser) { 122 CLog.i("Switching back from %d to %d", currentUserId, mInitialUserId); 123 switchUser(mInitialUserId); 124 } 125 126 if (!mUsersToBeRemoved.isEmpty()) { 127 CLog.i("Removing users %s", mUsersToBeRemoved); 128 for (int userId : mUsersToBeRemoved) { 129 removeUser(userId); 130 } 131 } 132 133 // Should have been removed above, but as the saying goes, better safe than sorry... 134 removeUsers(USER_PREFIX); 135 136 if (mInitialMaximumNumberOfUsers != null) { 137 CLog.i("Restoring max number of users to %d", mInitialMaximumNumberOfUsers); 138 setMaxNumberUsers(mInitialMaximumNumberOfUsers); 139 } 140 } 141 142 /** 143 * It is possible that during test initial user is deleted and it is not possible to switch to 144 * the initial User. This method controls if test should switch to initial user on clean up. 145 */ doNotSwitchToInitialUserAfterTest()146 public void doNotSwitchToInitialUserAfterTest() { 147 mSwitchToInitialUser = false; 148 } 149 150 /** 151 * Returns whether device is in headless system user mode. 152 */ isHeadlessSystemUserMode()153 boolean isHeadlessSystemUserMode() throws Exception { 154 String result = getDevice() 155 .executeShellCommand("getprop ro.fw.mu.headless_system_user").trim(); 156 return Boolean.valueOf(result); 157 } 158 159 /** 160 * Makes sure the device supports multiple users, throwing {@link AssumptionViolatedException} 161 * if it doesn't. 162 */ assumeSupportsMultipleUsers()163 protected final void assumeSupportsMultipleUsers() throws Exception { 164 assumeTrue("device does not support multi-user", 165 getDevice().getMaxNumberOfUsersSupported() > 1); 166 } 167 168 /** 169 * Makes sure the device can add {@code numberOfUsers} new users, increasing limit if needed or 170 * failing if not possible. 171 */ requiresExtraUsers(int numberOfUsers)172 protected final void requiresExtraUsers(int numberOfUsers) throws Exception { 173 assumeSupportsMultipleUsers(); 174 175 int maxNumber = getDevice().getMaxNumberOfUsersSupported(); 176 int currentNumber = getDevice().listUsers().size(); 177 178 if (currentNumber + numberOfUsers <= maxNumber) return; 179 180 if (!getDevice().isAdbRoot()) { 181 failCannotCreateUsers(numberOfUsers, currentNumber, maxNumber, /* isAdbRoot= */ false); 182 } 183 184 // Increase limit... 185 mInitialMaximumNumberOfUsers = maxNumber; 186 setMaxNumberUsers(maxNumber + numberOfUsers); 187 188 // ...and try again 189 maxNumber = getDevice().getMaxNumberOfUsersSupported(); 190 if (currentNumber + numberOfUsers > maxNumber) { 191 failCannotCreateUsers(numberOfUsers, currentNumber, maxNumber, /* isAdbRoot= */ true); 192 } 193 } 194 failCannotCreateUsers(int numberOfUsers, int currentNumber, int maxNumber, boolean isAdbRoot)195 private void failCannotCreateUsers(int numberOfUsers, int currentNumber, int maxNumber, 196 boolean isAdbRoot) { 197 String reason = isAdbRoot ? "failed to increase it" 198 : "cannot be increased without adb root"; 199 String existingUsers = ""; 200 try { 201 existingUsers = "Existing users: " + executeCommand("cmd user list --all -v"); 202 } catch (Exception e) { 203 // ignore 204 } 205 fail("Cannot create " + numberOfUsers + " users: current number is " + currentNumber 206 + ", limit is " + maxNumber + " and could not be increased (" + reason + "). " 207 + existingUsers); 208 } 209 210 /** 211 * Executes the shell command and returns the output. 212 */ executeCommand(String command, Object... args)213 protected String executeCommand(String command, Object... args) throws Exception { 214 String fullCommand = String.format(command, args); 215 return getDevice().executeShellCommand(fullCommand); 216 } 217 218 /** 219 * Executes the shell command and parses output with {@code resultParser}. 220 */ executeAndParseCommand(Function<String, T> resultParser, String command, Object... args)221 protected <T> T executeAndParseCommand(Function<String, T> resultParser, 222 String command, Object... args) throws Exception { 223 String output = executeCommand(command, args); 224 return resultParser.apply(output); 225 } 226 227 /** 228 * Executes the shell command and parses the Matcher output with {@code resultParser}, failing 229 * with {@code matchNotFoundErrorMessage} if it didn't match the {@code regex}. 230 */ executeAndParseCommand(Pattern regex, String matchNotFoundErrorMessage, Function<Matcher, T> resultParser, String command, Object... args)231 protected <T> T executeAndParseCommand(Pattern regex, String matchNotFoundErrorMessage, 232 Function<Matcher, T> resultParser, 233 String command, Object... args) throws Exception { 234 String output = executeCommand(command, args); 235 Matcher matcher = regex.matcher(output); 236 if (!matcher.find()) { 237 fail(matchNotFoundErrorMessage + ". Shell command: '" + String.format(command, args) 238 + "'. Output: " + output.trim() + ". Regex: " + regex); 239 } 240 return resultParser.apply(matcher); 241 } 242 243 /** 244 * Executes the shell command and parses the Matcher output with {@code resultParser}. 245 */ executeAndParseCommand(Pattern regex, Function<Matcher, T> resultParser, String command, Object... args)246 protected <T> T executeAndParseCommand(Pattern regex, Function<Matcher, T> resultParser, 247 String command, Object... args) throws Exception { 248 String output = executeCommand(command, args); 249 return resultParser.apply(regex.matcher(output)); 250 } 251 252 /** 253 * Executes the shell command that returns all users (including pre-created and partial) 254 * and returns {@code function} applied to them. 255 */ onAllUsers(Function<List<UserInfo>, T> function)256 public <T> T onAllUsers(Function<List<UserInfo>, T> function) throws Exception { 257 ArrayList<UserInfo> allUsers = executeAndParseCommand(USER_PATTERN, (matcher) -> { 258 ArrayList<UserInfo> users = new ArrayList<>(); 259 while (matcher.find()) { 260 users.add(new UserInfo(matcher)); 261 } 262 return users; 263 }, "cmd user list --all -v"); 264 return function.apply(allUsers); 265 } 266 267 /** 268 * Gets the info for the given user. 269 */ getUserInfo(int userId)270 public UserInfo getUserInfo(int userId) throws Exception { 271 return onAllUsers((allUsers) -> allUsers.stream() 272 .filter((u) -> u.id == userId)) 273 .findFirst().get(); 274 } 275 276 /** 277 * Gets all persistent (i.e., non-ephemeral) users. 278 */ getAllPersistentUsers()279 protected List<Integer> getAllPersistentUsers() throws Exception { 280 return onAllUsers((allUsers) -> allUsers.stream() 281 .filter((u) -> !u.flags.contains("DISABLED") && !u.flags.contains("EPHEMERAL") 282 && !u.otherState.contains("pre-created") 283 && !u.otherState.contains("partial")) 284 .map((u) -> u.id).collect(Collectors.toList())); 285 } 286 287 /** 288 * Sets the maximum number of users that can be created for this car. 289 * 290 * @throws IllegalStateException if adb is not running as root 291 */ setMaxNumberUsers(int numUsers)292 protected void setMaxNumberUsers(int numUsers) throws Exception { 293 if (!getDevice().isAdbRoot()) { 294 throw new IllegalStateException("must be running adb root"); 295 } 296 executeCommand("setprop fw.max_users %d", numUsers); 297 } 298 299 /** 300 * Gets the current user's id. 301 */ getCurrentUserId()302 protected int getCurrentUserId() throws DeviceNotAvailableException { 303 return getDevice().getCurrentUser(); 304 } 305 306 /** 307 * Creates a full user with car service shell command. 308 */ createFullUser(String name)309 protected int createFullUser(String name) throws Exception { 310 return createUser(name, /* flags= */ 0, /* isGuest= */ false); 311 } 312 313 /** 314 * Creates a full guest with car service shell command. 315 */ createGuestUser(String name)316 protected int createGuestUser(String name) throws Exception { 317 return createUser(name, /* flags= */ 0, /* isGuest= */ true); 318 } 319 320 /** 321 * Creates a flexible user with car service shell command. 322 * 323 * <p><b>NOTE: </b>it uses User HAL flags, not core Android's. 324 */ createUser(String name, int flags, boolean isGuest)325 protected int createUser(String name, int flags, boolean isGuest) throws Exception { 326 name = USER_PREFIX + "." + name; 327 waitForCarServiceReady(); 328 int userId = executeAndParseCommand(CREATE_USER_OUTPUT_PATTERN, 329 "Could not create user with name " + name 330 + ", flags " + flags + ", guest " + isGuest, 331 matcher -> Integer.parseInt(matcher.group(1)), 332 "cmd car_service create-user --flags %d %s%s", 333 flags, (isGuest ? "--guest " : ""), name); 334 markUserForRemovalAfterTest(userId); 335 return userId; 336 } 337 338 /** 339 * Marks a user to be removed at the end of the tests. 340 */ markUserForRemovalAfterTest(int userId)341 protected void markUserForRemovalAfterTest(int userId) { 342 mUsersToBeRemoved.add(userId); 343 } 344 345 /** 346 * Waits until the given user is initialized. 347 */ waitForUserInitialized(int userId)348 protected void waitForUserInitialized(int userId) throws Exception { 349 CommonTestUtils.waitUntil("timed out waiting for user " + userId + " initialization", 350 DEFAULT_TIMEOUT_SEC, () -> isUserInitialized(userId)); 351 } 352 353 /** 354 * Waits until the system server is ready. 355 */ waitForCarServiceReady()356 protected void waitForCarServiceReady() throws Exception { 357 CommonTestUtils.waitUntil("timed out waiting for car service", 358 DEFAULT_TIMEOUT_SEC, () -> isCarServiceReady()); 359 } 360 isCarServiceReady()361 protected boolean isCarServiceReady() { 362 String cmd = "service check car_service"; 363 try { 364 String output = getDevice().executeShellCommand(cmd); 365 return !output.endsWith("not found"); 366 } catch (Exception e) { 367 CLog.i("%s failed: %s", cmd, e.getMessage()); 368 } 369 return false; 370 } 371 372 /** 373 * Asserts that the given user is initialized. 374 */ assertUserInitialized(int userId)375 protected void assertUserInitialized(int userId) throws Exception { 376 assertWithMessage("User %s not initialized", userId).that(isUserInitialized(userId)) 377 .isTrue(); 378 CLog.v("User %d is initialized", userId); 379 } 380 381 /** 382 * Checks if the given user is initialized. 383 */ isUserInitialized(int userId)384 protected boolean isUserInitialized(int userId) throws Exception { 385 UserInfo userInfo = getUserInfo(userId); 386 CLog.v("isUserInitialized(%d): %s", userId, userInfo); 387 return userInfo.flags.contains("INITIALIZED"); 388 } 389 390 /** 391 * Checks if the given user is ephemeral. 392 */ isUserEphemeral(int userId)393 protected boolean isUserEphemeral(int userId) throws Exception { 394 UserInfo userInfo = getUserInfo(userId); 395 CLog.v("isUserEphemeral(%d): %s", userId, userInfo); 396 return userInfo.flags.contains("EPHEMERAL"); 397 } 398 399 /** 400 * Switches the current user. 401 */ switchUser(int userId)402 protected void switchUser(int userId) throws Exception { 403 waitForCarServiceReady(); 404 String output = executeCommand("cmd car_service switch-user %d", userId); 405 if (!output.contains("STATUS_SUCCESSFUL")) { 406 throw new IllegalStateException("Failed to switch to user " + userId + ": " + output); 407 } 408 waitUntilCurrentUser(userId); 409 } 410 411 /** 412 * Waits until the given user is the current foreground user. 413 */ waitUntilCurrentUser(int userId)414 protected void waitUntilCurrentUser(int userId) throws Exception { 415 CommonTestUtils.waitUntil("timed out (" + DEFAULT_TIMEOUT_SEC 416 + "s) waiting for current user to be " + userId 417 + " (it is " + getCurrentUserId() + ")", DEFAULT_TIMEOUT_SEC, 418 () -> (getCurrentUserId() == userId)); 419 } 420 421 /** 422 * Waits until the current user is not the system user. 423 */ waitUntilCurrentUserIsNotSystem(int timeoutSec)424 protected void waitUntilCurrentUserIsNotSystem(int timeoutSec) throws Exception { 425 CommonTestUtils.waitUntil("timed out (" + timeoutSec + "s) waiting for current user to NOT " 426 + "be the system user", timeoutSec, () -> getCurrentUserId() != SYSTEM_USER_ID); 427 } 428 429 /** 430 * Waits until {@code n} persistent (i.e., non-ephemeral) users are available. 431 */ waitUntilAtLeastNPersistentUsersAreAvailable(int timeoutSec, int n)432 protected void waitUntilAtLeastNPersistentUsersAreAvailable(int timeoutSec, int n) 433 throws Exception { 434 waitUntil(timeoutSec, () -> getAllPersistentUsers().size() >= n, "%d persistent users", n); 435 } 436 437 /** 438 * Waits until the given condition is reached. 439 */ waitUntil(long timeoutSeconds, BooleanSupplierWithThrow<Exception> predicate, String msgPattern, Object... msgArgs)440 protected void waitUntil(long timeoutSeconds, BooleanSupplierWithThrow<Exception> predicate, 441 String msgPattern, Object... msgArgs) throws Exception { 442 CommonTestUtils.waitUntil("timed out (" + timeoutSeconds + "s) waiting for " 443 + String.format(msgPattern, msgArgs), timeoutSeconds, predicate); 444 } 445 446 // TODO(b/230500604): refactor other CommonTestUtils.waitUntil() calls to use this one insteads 447 /** 448 * Waits until the given condition is reached, using the default timeout. 449 */ waitUntil(BooleanSupplierWithThrow<Exception> predicate, String msgPattern, Object... msgArgs)450 protected void waitUntil(BooleanSupplierWithThrow<Exception> predicate, 451 String msgPattern, Object... msgArgs) throws Exception { 452 waitUntil(DEFAULT_TIMEOUT_SEC, predicate, msgPattern, msgArgs); 453 } 454 455 /** 456 * Removes a user by user ID and update the list of users to be removed. 457 */ removeUser(int userId)458 protected boolean removeUser(int userId) throws Exception { 459 String result = executeCommand("cmd car_service remove-user %d", userId); 460 if (result.contains("STATUS_SUCCESSFUL")) { 461 return true; 462 } 463 return false; 464 } 465 466 /** 467 * Removes users whose name start with the given prefix. 468 */ removeUsers(String prefix)469 protected void removeUsers(String prefix) throws Exception { 470 Pattern pattern = Pattern.compile("^.*id=(\\d+), name=(" + prefix + ".*),.*$"); 471 String output = executeCommand("cmd user list --all -v"); 472 for (String line : output.split("\\n")) { 473 Matcher matcher = pattern.matcher(line); 474 if (!matcher.find()) continue; 475 476 int userId = Integer.parseInt(matcher.group(1)); 477 String name = matcher.group(2); 478 CLog.e("Removing user with %s prefix (id=%d, name='%s')", prefix, userId, name); 479 removeUser(userId); 480 } 481 } 482 483 /** 484 * Checks if an app is installed for a given user. 485 */ isAppInstalledForUser(String packageName, int userId)486 protected boolean isAppInstalledForUser(String packageName, int userId) 487 throws DeviceNotAvailableException { 488 return getDevice().isPackageInstalled(packageName, Integer.toString(userId)); 489 } 490 491 /** 492 * Fails the test if the app is installed for the given user. 493 */ assertAppInstalledForUser(String packageName, int userId)494 protected void assertAppInstalledForUser(String packageName, int userId) 495 throws DeviceNotAvailableException { 496 assertWithMessage("%s should BE installed for user %s", packageName, userId).that( 497 isAppInstalledForUser(packageName, userId)).isTrue(); 498 } 499 500 /** 501 * Fails the test if the app is NOT installed for the given user. 502 */ assertAppNotInstalledForUser(String packageName, int userId)503 protected void assertAppNotInstalledForUser(String packageName, int userId) 504 throws DeviceNotAvailableException { 505 assertWithMessage("%s should NOT be installed for user %s", packageName, userId).that( 506 isAppInstalledForUser(packageName, userId)).isFalse(); 507 } 508 509 /** 510 * Restarts the system server process. 511 * 512 * <p>Useful for cases where the test case changes system properties, as 513 * {@link ITestDevice#reboot()} would reset them. 514 */ restartSystemServer()515 protected void restartSystemServer() throws Exception { 516 long uptimeBefore = getSystemServerUptime(); 517 CLog.d("Uptime before restart: %d", uptimeBefore); 518 519 restartOrReboot(); 520 521 getDevice().waitForDeviceAvailable(); 522 523 // Also checks for uptime - it might be an overkill, but at least it will add more logs, 524 // which could help in case of issues 525 CommonTestUtils.waitUntil("timed out waiting until for new system server uptime", 526 SYSTEM_RESTART_TIMEOUT_SEC, () -> { 527 long uptimeAfter = getSystemServerUptime(); 528 CLog.d("Uptime after restart: %d", uptimeAfter); 529 return uptimeAfter != -1 && uptimeAfter != uptimeBefore; 530 }); 531 532 waitForCarServiceReady(); 533 } 534 restartOrReboot()535 private void restartOrReboot() throws DeviceNotAvailableException { 536 ITestDevice device = getDevice(); 537 538 if (device.isAdbRoot()) { 539 CLog.d("Restarting system server"); 540 device.executeShellCommand("stop"); 541 device.executeShellCommand("start"); 542 return; 543 } 544 545 CLog.d("Only root user can restart system server; rebooting instead"); 546 getDevice().reboot(); 547 } 548 549 /** 550 * Gets the system server uptime (or {@code -1} if not available). 551 */ getSystemServerUptime()552 protected long getSystemServerUptime() throws DeviceNotAvailableException { 553 return getDevice().getIntProperty("sys.system_server.start_uptime", -1); 554 } 555 556 /** 557 * Reboots the device. 558 */ reboot()559 protected void reboot() throws Exception { 560 CLog.d("Rebooting device"); 561 getDevice().reboot(); 562 } 563 564 /** 565 * Gets mapping of package and permissions granted for requested user id. 566 * 567 * @return Map<String, List<String>> where key is the package name and 568 * the value is list of permissions granted for this user. 569 */ getPackagesAndPermissionsForUser(int userId)570 protected Map<String, List<String>> getPackagesAndPermissionsForUser(int userId) 571 throws Exception { 572 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver(); 573 getDevice().executeShellCommand("dumpsys package --proto", receiver); 574 575 PackageServiceDumpProto dump = PackageServiceDumpProto.parser() 576 .parseFrom(receiver.getOutput()); 577 578 CLog.v("Device has %d packages while getPackagesAndPermissions", dump.getPackagesCount()); 579 Map<String, List<String>> pkgMap = new HashMap<>(); 580 for (PackageProto pkg : dump.getPackagesList()) { 581 String pkgName = pkg.getName(); 582 for (UserPermissionsProto userPermissions : pkg.getUserPermissionsList()) { 583 if (userPermissions.getId() == userId) { 584 pkgMap.put(pkg.getName(), userPermissions.getGrantedPermissionsList()); 585 break; 586 } 587 } 588 } 589 return pkgMap; 590 } 591 592 /** 593 * Checks if the given package has a process running on the device. 594 */ isPackageRunning(String packageName)595 protected boolean isPackageRunning(String packageName) throws Exception { 596 return !executeCommand("pidof %s", packageName).isEmpty(); 597 } 598 599 /** 600 * Sleeps for the given amount of milliseconds. 601 */ sleep(long ms)602 protected void sleep(long ms) throws InterruptedException { 603 CLog.v("Sleeping for %dms", ms); 604 Thread.sleep(ms); 605 CLog.v("Woke up; Little Susie woke up!"); 606 } 607 608 // TODO(b/169341308): move to common infra code 609 private static final class RequiredFeatureRule implements TestRule { 610 611 private final ITestInformationReceiver mReceiver; 612 private final String mFeature; 613 RequiredFeatureRule(ITestInformationReceiver receiver, String feature)614 RequiredFeatureRule(ITestInformationReceiver receiver, String feature) { 615 mReceiver = receiver; 616 mFeature = feature; 617 } 618 619 @Override apply(Statement base, Description description)620 public Statement apply(Statement base, Description description) { 621 return new Statement() { 622 623 @Override 624 public void evaluate() throws Throwable { 625 boolean hasFeature = false; 626 try { 627 hasFeature = mReceiver.getTestInformation().getDevice() 628 .hasFeature(mFeature); 629 } catch (DeviceNotAvailableException e) { 630 CLog.e("Could not check if device has feature %s: %e", mFeature, e); 631 return; 632 } 633 634 if (!hasFeature) { 635 CLog.d("skipping %s#%s" 636 + " because device does not have feature '%s'", 637 description.getClassName(), description.getMethodName(), mFeature); 638 throw new AssumptionViolatedException("Device does not have feature '" 639 + mFeature + "'"); 640 } 641 base.evaluate(); 642 } 643 }; 644 } 645 646 @Override toString()647 public String toString() { 648 return "RequiredFeatureRule[" + mFeature + "]"; 649 } 650 } 651 652 /** 653 * Represents a user as returned by {@code cmd user list -v}. 654 */ 655 public static final class UserInfo { 656 public final int id; 657 public final String flags; 658 public final String name; 659 public final String otherState; 660 661 private UserInfo(Matcher matcher) { 662 id = Integer.parseInt(matcher.group(USER_PATTERN_GROUP_ID)); 663 flags = matcher.group(USER_PATTERN_GROUP_FLAGS); 664 name = matcher.group(USER_PATTERN_GROUP_NAME); 665 otherState = matcher.group(USER_PATTERN_GROUP_OTHER_STATE); 666 } 667 668 @Override 669 public String toString() { 670 return "[UserInfo: id=" + id + ", flags=" + flags + ", name=" + name 671 + ", otherState=" + otherState + "]"; 672 } 673 } 674 } 675