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.bedstead.nene.users; 18 19 import static android.os.Build.VERSION.SDK_INT; 20 import static android.os.Process.myUserHandle; 21 22 import static com.android.bedstead.nene.users.UserType.MANAGED_PROFILE_TYPE_NAME; 23 import static com.android.bedstead.nene.users.UserType.SECONDARY_USER_TYPE_NAME; 24 import static com.android.bedstead.nene.users.UserType.SYSTEM_USER_TYPE_NAME; 25 26 import android.os.Build; 27 import android.os.UserHandle; 28 29 import androidx.annotation.CheckResult; 30 import androidx.annotation.Nullable; 31 32 import com.android.bedstead.nene.TestApis; 33 import com.android.bedstead.nene.exceptions.AdbException; 34 import com.android.bedstead.nene.exceptions.AdbParseException; 35 import com.android.bedstead.nene.exceptions.NeneException; 36 import com.android.bedstead.nene.utils.ShellCommand; 37 import com.android.compatibility.common.util.PollingCheck; 38 39 import java.util.Collection; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.Iterator; 43 import java.util.Map; 44 import java.util.Set; 45 import java.util.concurrent.atomic.AtomicReference; 46 import java.util.function.Function; 47 import java.util.stream.Collectors; 48 49 public final class Users { 50 51 static final int SYSTEM_USER_ID = 0; 52 private static final long WAIT_FOR_USER_TIMEOUT_MS = 1000 * 60; 53 54 private Map<Integer, User> mCachedUsers = null; 55 private Map<String, UserType> mCachedUserTypes = null; 56 private Set<UserType> mCachedUserTypeValues = null; 57 private final AdbUserParser mParser; 58 private final TestApis mTestApis; 59 Users(TestApis testApis)60 public Users(TestApis testApis) { 61 mTestApis = testApis; 62 mParser = AdbUserParser.get(mTestApis, SDK_INT); 63 } 64 65 /** Get all {@link User}s on the device. */ all()66 public Collection<User> all() { 67 fillCache(); 68 69 return mCachedUsers.values(); 70 } 71 72 /** Get a {@link UserReference} for the user running the current test process. */ instrumented()73 public UserReference instrumented() { 74 return find(myUserHandle()); 75 } 76 77 /** Get a {@link UserReference} for the system user. */ system()78 public UserReference system() { 79 return find(0); 80 } 81 82 /** Get a {@link UserReference} by {@code id}. */ find(int id)83 public UserReference find(int id) { 84 return new UnresolvedUser(mTestApis, id); 85 } 86 87 /** Get a {@link UserReference} by {@code userHandle}. */ find(UserHandle userHandle)88 public UserReference find(UserHandle userHandle) { 89 return new UnresolvedUser(mTestApis, userHandle.getIdentifier()); 90 } 91 92 @Nullable fetchUser(int id)93 User fetchUser(int id) { 94 // TODO(scottjonathan): fillCache probably does more than we need here - 95 // can we make it more efficient? 96 fillCache(); 97 98 return mCachedUsers.get(id); 99 } 100 101 /** Get all supported {@link UserType}s. */ supportedTypes()102 public Set<UserType> supportedTypes() { 103 ensureSupportedTypesCacheFilled(); 104 return mCachedUserTypeValues; 105 } 106 107 /** Get a {@link UserType} with the given {@code typeName}, or {@code null} */ supportedType(String typeName)108 public UserType supportedType(String typeName) { 109 ensureSupportedTypesCacheFilled(); 110 return mCachedUserTypes.get(typeName); 111 } 112 113 /** 114 * Find all users which have the given {@link UserType}. 115 */ findUsersOfType(UserType userType)116 public Set<UserReference> findUsersOfType(UserType userType) { 117 if (userType == null) { 118 throw new NullPointerException(); 119 } 120 121 if (userType.baseType().contains(UserType.BaseType.PROFILE)) { 122 throw new NeneException("Cannot use findUsersOfType with profile type " + userType); 123 } 124 125 return all().stream() 126 .filter(u -> u.type().equals(userType)) 127 .collect(Collectors.toSet()); 128 } 129 130 /** 131 * Find a single user which has the given {@link UserType}. 132 * 133 * <p>If there are no users of the given type, {@code Null} will be returned. 134 * 135 * <p>If there is more than one user of the given type, {@link NeneException} will be thrown. 136 */ 137 @Nullable findUserOfType(UserType userType)138 public UserReference findUserOfType(UserType userType) { 139 Set<UserReference> users = findUsersOfType(userType); 140 141 if (users.isEmpty()) { 142 return null; 143 } else if (users.size() > 1) { 144 throw new NeneException("findUserOfType called but there is more than 1 user of type " 145 + userType + ". Found: " + users); 146 } 147 148 return users.iterator().next(); 149 } 150 151 /** 152 * Find all users which have the given {@link UserType} and the given parent. 153 */ findProfilesOfType(UserType userType, UserReference parent)154 public Set<UserReference> findProfilesOfType(UserType userType, UserReference parent) { 155 if (userType == null || parent == null) { 156 throw new NullPointerException(); 157 } 158 159 if (!userType.baseType().contains(UserType.BaseType.PROFILE)) { 160 throw new NeneException("Cannot use findProfilesOfType with non-profile type " 161 + userType); 162 } 163 164 return all().stream() 165 .filter(u -> parent.equals(u.parent()) 166 && u.type().equals(userType)) 167 .collect(Collectors.toSet()); 168 } 169 170 /** 171 * Find all users which have the given {@link UserType} and the given parent. 172 * 173 * <p>If there are no users of the given type and parent, {@code Null} will be returned. 174 * 175 * <p>If there is more than one user of the given type and parent, {@link NeneException} will 176 * be thrown. 177 */ 178 @Nullable findProfileOfType(UserType userType, UserReference parent)179 public UserReference findProfileOfType(UserType userType, UserReference parent) { 180 Set<UserReference> profiles = findProfilesOfType(userType, parent); 181 182 if (profiles.isEmpty()) { 183 return null; 184 } else if (profiles.size() > 1) { 185 throw new NeneException("findProfileOfType called but there is more than 1 user of " 186 + "type " + userType + " with parent " + parent + ". Found: " + profiles); 187 } 188 189 return profiles.iterator().next(); 190 } 191 ensureSupportedTypesCacheFilled()192 private void ensureSupportedTypesCacheFilled() { 193 if (mCachedUserTypes != null) { 194 // SupportedTypes don't change so don't need to be refreshed 195 return; 196 } 197 if (SDK_INT < Build.VERSION_CODES.R) { 198 mCachedUserTypes = new HashMap<>(); 199 mCachedUserTypes.put(MANAGED_PROFILE_TYPE_NAME, managedProfileUserType()); 200 mCachedUserTypes.put(SYSTEM_USER_TYPE_NAME, systemUserType()); 201 mCachedUserTypes.put(SECONDARY_USER_TYPE_NAME, secondaryUserType()); 202 mCachedUserTypeValues = new HashSet<>(); 203 mCachedUserTypeValues.addAll(mCachedUserTypes.values()); 204 return; 205 } 206 207 fillCache(); 208 } 209 managedProfileUserType()210 private UserType managedProfileUserType() { 211 UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType(); 212 managedProfileMutableUserType.mName = MANAGED_PROFILE_TYPE_NAME; 213 managedProfileMutableUserType.mBaseType = Set.of(UserType.BaseType.PROFILE); 214 managedProfileMutableUserType.mEnabled = true; 215 managedProfileMutableUserType.mMaxAllowed = -1; 216 managedProfileMutableUserType.mMaxAllowedPerParent = 1; 217 return new UserType(managedProfileMutableUserType); 218 } 219 systemUserType()220 private UserType systemUserType() { 221 UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType(); 222 managedProfileMutableUserType.mName = SYSTEM_USER_TYPE_NAME; 223 managedProfileMutableUserType.mBaseType = 224 Set.of(UserType.BaseType.FULL, UserType.BaseType.SYSTEM); 225 managedProfileMutableUserType.mEnabled = true; 226 managedProfileMutableUserType.mMaxAllowed = -1; 227 managedProfileMutableUserType.mMaxAllowedPerParent = -1; 228 return new UserType(managedProfileMutableUserType); 229 } 230 secondaryUserType()231 private UserType secondaryUserType() { 232 UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType(); 233 managedProfileMutableUserType.mName = SECONDARY_USER_TYPE_NAME; 234 managedProfileMutableUserType.mBaseType = Set.of(UserType.BaseType.FULL); 235 managedProfileMutableUserType.mEnabled = true; 236 managedProfileMutableUserType.mMaxAllowed = -1; 237 managedProfileMutableUserType.mMaxAllowedPerParent = -1; 238 return new UserType(managedProfileMutableUserType); 239 } 240 241 /** 242 * Create a new user. 243 */ 244 @CheckResult createUser()245 public UserBuilder createUser() { 246 return new UserBuilder(mTestApis); 247 } 248 249 /** 250 * Get a {@link UserReference} to a user who does not exist. 251 */ nonExisting()252 public UserReference nonExisting() { 253 fillCache(); 254 int id = 0; 255 256 while (mCachedUsers.get(id) != null) { 257 id++; 258 } 259 260 return new UnresolvedUser(mTestApis, id); 261 } 262 fillCache()263 private void fillCache() { 264 try { 265 // TODO: Replace use of adb on supported versions of Android 266 String userDumpsysOutput = ShellCommand.builder("dumpsys user").execute(); 267 AdbUserParser.ParseResult result = mParser.parse(userDumpsysOutput); 268 269 mCachedUsers = result.mUsers; 270 if (result.mUserTypes != null) { 271 mCachedUserTypes = result.mUserTypes; 272 } else { 273 ensureSupportedTypesCacheFilled(); 274 } 275 276 Iterator<Map.Entry<Integer, User>> iterator = mCachedUsers.entrySet().iterator(); 277 278 while (iterator.hasNext()) { 279 Map.Entry<Integer, User> entry = iterator.next(); 280 281 if (entry.getValue().isRemoving()) { 282 // We don't expose users who are currently being removed 283 iterator.remove(); 284 continue; 285 } 286 287 User.MutableUser mutableUser = entry.getValue().mMutableUser; 288 289 if (SDK_INT < Build.VERSION_CODES.R) { 290 if (entry.getValue().id() == SYSTEM_USER_ID) { 291 mutableUser.mType = supportedType(SYSTEM_USER_TYPE_NAME); 292 mutableUser.mIsPrimary = true; 293 } else if (entry.getValue().hasFlag(User.FLAG_MANAGED_PROFILE)) { 294 mutableUser.mType = 295 supportedType(MANAGED_PROFILE_TYPE_NAME); 296 mutableUser.mIsPrimary = false; 297 } else { 298 mutableUser.mType = 299 supportedType(SECONDARY_USER_TYPE_NAME); 300 mutableUser.mIsPrimary = false; 301 } 302 } 303 304 if (SDK_INT < Build.VERSION_CODES.S) { 305 if (mutableUser.mType.baseType() 306 .contains(UserType.BaseType.PROFILE)) { 307 // We assume that all profiles before S were on the System User 308 mutableUser.mParent = find(SYSTEM_USER_ID); 309 } 310 } 311 } 312 313 mCachedUserTypeValues = new HashSet<>(); 314 mCachedUserTypeValues.addAll(mCachedUserTypes.values()); 315 316 } catch (AdbException | AdbParseException e) { 317 throw new RuntimeException("Error filling cache", e); 318 } 319 } 320 321 /** 322 * Block until the user with the given {@code userReference} exists and is in the correct state. 323 * 324 * <p>If this cannot be met before a timeout, a {@link NeneException} will be thrown. 325 */ waitForUserToMatch(UserReference userReference, Function<User, Boolean> userChecker)326 User waitForUserToMatch(UserReference userReference, Function<User, Boolean> userChecker) { 327 return waitForUserToMatch(userReference, userChecker, /* waitForExist= */ true); 328 } 329 330 /** 331 * Block until the user with the given {@code userReference} to not exist or to be in the 332 * correct state. 333 * 334 * <p>If this cannot be met before a timeout, a {@link NeneException} will be thrown. 335 */ 336 @Nullable waitForUserToNotExistOrMatch( UserReference userReference, Function<User, Boolean> userChecker)337 User waitForUserToNotExistOrMatch( 338 UserReference userReference, Function<User, Boolean> userChecker) { 339 return waitForUserToMatch(userReference, userChecker, /* waitForExist= */ false); 340 } 341 342 @Nullable waitForUserToMatch( UserReference userReference, Function<User, Boolean> userChecker, boolean waitForExist)343 private User waitForUserToMatch( 344 UserReference userReference, Function<User, Boolean> userChecker, 345 boolean waitForExist) { 346 // TODO(scottjonathan): This is pretty heavy because we resolve everything when we know we 347 // are throwing away everything except one user. Optimise 348 try { 349 AtomicReference<User> returnUser = new AtomicReference<>(); 350 PollingCheck.waitFor(WAIT_FOR_USER_TIMEOUT_MS, () -> { 351 User user = userReference.resolve(); 352 returnUser.set(user); 353 if (user == null) { 354 return !waitForExist; 355 } 356 return userChecker.apply(user); 357 }); 358 return returnUser.get(); 359 } catch (AssertionError e) { 360 User user = userReference.resolve(); 361 362 if (user == null) { 363 throw new NeneException( 364 "Timed out waiting for user state for user " 365 + userReference + ". User does not exist.", e); 366 } 367 throw new NeneException( 368 "Timed out waiting for user state, current state " + user, e 369 ); 370 } 371 } 372 } 373