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