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