• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.tradefed.targetprep;
18 
19 import static com.android.tradefed.targetprep.UserHelper.RUN_TESTS_AS_USER_KEY;
20 
21 import com.android.tradefed.config.Option;
22 import com.android.tradefed.config.OptionClass;
23 import com.android.tradefed.device.DeviceNotAvailableException;
24 import com.android.tradefed.device.ITestDevice;
25 import com.android.tradefed.device.UserInfo;
26 import com.android.tradefed.invoker.TestInformation;
27 
28 import com.google.common.annotations.VisibleForTesting;
29 
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.Comparator;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36 
37 /**
38  * An {@link ITargetPreparer} to ensure that the test runs as a secondary user. In addition, if
39  * the option {@link START_BACKGROUND_USER} is {@code true} and the current user is already
40  * a secondary user, it will ensure that there is a visble background secondary user run on a
41  * secondary display.
42  *
43  * <p>If the target secondary user doesn't exist, it will create a new one and remove it in
44  * teardown. Otherwise, it will be used rather than creating a new one, and it will not be removed
45  * in teardown.
46  *
47  * <p>If the device does not have capacity to create a new user when one is required, then the
48  * instrumentation argument skip-tests-reason will be set, and the user will not be changed. Tests
49  * running on the device can read this argument to respond to this state.
50  */
51 @OptionClass(alias = "run-on-secondary-user")
52 public class RunOnSecondaryUserTargetPreparer extends BaseTargetPreparer {
53 
54     @VisibleForTesting static final String TEST_PACKAGE_NAME_OPTION = "test-package-name";
55 
56     @VisibleForTesting static final String SKIP_TESTS_REASON_KEY = "skip-tests-reason";
57 
58     @VisibleForTesting static final String START_BACKGROUND_USER = "start-background-user";
59 
60     private int userIdToDelete = -1;
61     private int originalUserId;
62 
63     @Option(
64             name = TEST_PACKAGE_NAME_OPTION,
65             description =
66                     "the name of a package to be installed on the secondary user. "
67                             + "This must already be installed on the device.",
68             importance = Option.Importance.IF_UNSET)
69     private List<String> mTestPackages = new ArrayList<>();
70 
71     @Option(
72             name = START_BACKGROUND_USER,
73             description =
74                     "If true and the current user is a secondary user, it will create a "
75                             + "background secondary user (if such user doesn't exist) and "
76                             + "start the background user on secondary display")
77     private boolean mStartBackgroundUser;
78 
79     @Override
setUp(TestInformation testInfo)80     public void setUp(TestInformation testInfo)
81             throws TargetSetupError, DeviceNotAvailableException {
82         removeNonForTestingUsers(testInfo.getDevice());
83 
84         originalUserId = testInfo.getDevice().getCurrentUser();
85         // This must be a for-testing user because we removed the not-for-testing ones
86         int secondaryUserId = getTargetSecondaryUserId(testInfo.getDevice());
87 
88         if (secondaryUserId == originalUserId) {
89             return;
90         }
91 
92         if (secondaryUserId == -1) {
93             if (!assumeTrue(
94                     canCreateAdditionalUsers(testInfo.getDevice(), 1),
95                     "Device cannot support additional users",
96                     testInfo)) {
97                 return;
98             }
99 
100             secondaryUserId = createSecondaryUser(testInfo.getDevice());
101             userIdToDelete = secondaryUserId;
102         }
103 
104         // The wait flag is only supported on Android 29+
105         boolean waitFlag = testInfo.getDevice().getApiLevel() >= 29;
106         if (!testInfo.getDevice().isUserSecondary(originalUserId)) {
107 
108             testInfo.getDevice().startUser(secondaryUserId, waitFlag);
109             testInfo.getDevice().switchUser(secondaryUserId);
110             testInfo.properties().put(RUN_TESTS_AS_USER_KEY, Integer.toString(secondaryUserId));
111         } else {
112             Set<Integer> secondaryDisplayIdSet =
113                     testInfo.getDevice().listDisplayIdsForStartingVisibleBackgroundUsers();
114             if (!assumeTrue(
115                     !secondaryDisplayIdSet.isEmpty(),
116                     "This device has no secondary display",
117                     testInfo)) {
118                 return;
119             }
120             int secondaryDisplayId = secondaryDisplayIdSet.stream().findFirst().get();
121             testInfo.getDevice()
122                     .startVisibleBackgroundUser(secondaryUserId, secondaryDisplayId, waitFlag);
123         }
124         for (String pkg : mTestPackages) {
125             testInfo.getDevice()
126                     .executeShellCommand(
127                             "pm install-existing --user " + secondaryUserId + " " + pkg);
128         }
129 
130         testInfo.getDevice().executeShellCommand("pm list packages --user all -U");
131     }
132 
133     /** Get the id of a target secondary user currently on the device. -1 if there is none. */
getTargetSecondaryUserId(ITestDevice device)134     private int getTargetSecondaryUserId(ITestDevice device) throws DeviceNotAvailableException {
135         for (Map.Entry<Integer, UserInfo> userInfo : device.getUserInfos().entrySet()) {
136             if (!userInfo.getValue().isSecondary()) {
137                 continue;
138             }
139             // If mStartBackgroundUser is true and the current user is a secondary user,
140             // we need the target secondary user to be a non-current user (For example, on AAOS
141             // the current user is user 10, if mStartBackgroundUser is true, we need to create user
142             // 11). Otherwise, any secondary user is fine.
143             if (mStartBackgroundUser && device.isUserSecondary(originalUserId)) {
144                 if (userInfo.getValue().userId() != originalUserId) {
145                     return userInfo.getKey();
146                 }
147             } else {
148                 return userInfo.getKey();
149             }
150         }
151         return -1;
152     }
153 
154     /** Creates a secondary user and returns the new user ID. */
createSecondaryUser(ITestDevice device)155     private static int createSecondaryUser(ITestDevice device) throws DeviceNotAvailableException {
156         return device.createUser(
157                 "secondary", /* guest= */ false, /* ephemeral= */ false, /* forTesting= */ true);
158     }
159 
160     @Override
tearDown(TestInformation testInfo, Throwable e)161     public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
162         String value = testInfo.properties().remove(SKIP_TESTS_REASON_KEY);
163         if (value != null) {
164             // Skip teardown if a skip test reason was set.
165             return;
166         }
167 
168         testInfo.properties().remove(RUN_TESTS_AS_USER_KEY);
169 
170         ITestDevice device = testInfo.getDevice();
171         int currentUser = device.getCurrentUser();
172 
173         if (currentUser != originalUserId) {
174             device.switchUser(originalUserId);
175         }
176         if (userIdToDelete != -1) {
177             device.removeUser(userIdToDelete);
178         }
179     }
180 
181     /**
182      * Disable teardown and set the {@link #SKIP_TESTS_REASON_KEY} if {@code value} isn't true.
183      *
184      * <p>This will return {@code value} and, if it is not true, setup should be skipped.
185      */
assumeTrue(boolean value, String reason, TestInformation testInfo)186     private boolean assumeTrue(boolean value, String reason, TestInformation testInfo) {
187         if (!value) {
188             testInfo.properties().put(SKIP_TESTS_REASON_KEY, reason.replace(" ", "\\ "));
189         }
190 
191         return value;
192     }
193 
194     /**
195      * Remove all non for-testing users.
196      *
197      * <p>For a headless device, if {@code mStartBackgroundUser} is true, it would remove every non
198      * for-testing user except the first two secondary users and the system user; otherwise, it
199      * would remove every non for-testing user except the first secondary user and the system user.
200      *
201      * <p>For a non-headless device, it would remove every non for-testing user except the system
202      * user.
203      *
204      * <p>A communal profile is never removed.
205      */
removeNonForTestingUsers(ITestDevice device)206     private void removeNonForTestingUsers(ITestDevice device) throws DeviceNotAvailableException {
207         Map<Integer, UserInfo> userInfoMap = device.getUserInfos();
208 
209         List<UserInfo> userInfos = new ArrayList<>(userInfoMap.values());
210         Collections.sort(userInfos, Comparator.comparing(UserInfo::userId));
211 
212         int maxSkippedUsers =
213                 device.isHeadlessSystemUserMode() ? (mStartBackgroundUser ? 2 : 1) : 0;
214         int skippedUsers = 0;
215 
216         for (UserInfo userInfo : userInfos) {
217             if (isForTesting(userInfo)) {
218                 continue;
219             }
220 
221             if (skippedUsers < maxSkippedUsers) {
222                 skippedUsers++;
223                 continue;
224             }
225 
226             device.removeUser(userInfo.userId());
227         }
228     }
229 
isForTesting(UserInfo userInfo)230     private static boolean isForTesting(UserInfo userInfo) {
231         return userInfo.isSystem()
232                 || userInfo.isFlagForTesting()
233                 // Communal profile doesn't align with DPM implementation - it's only acceptable
234                 // here for now because no test with communal profile also uses enterprise
235                 || userInfo.isCommunalProfile();
236     }
237 
238     /** Checks whether it is possible to create the desired number of users. */
canCreateAdditionalUsers(ITestDevice device, int numberOfUsers)239     protected boolean canCreateAdditionalUsers(ITestDevice device, int numberOfUsers)
240             throws DeviceNotAvailableException {
241         return device.listUsers().size() + numberOfUsers <= device.getMaxNumberOfUsersSupported();
242     }
243 }
244