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.Manifest.permission.CREATE_USERS; 20 import static android.Manifest.permission.INTERACT_ACROSS_USERS; 21 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; 22 import static android.os.Build.VERSION.SDK_INT; 23 import static android.os.Build.VERSION_CODES.S; 24 import static android.os.Build.VERSION_CODES.S_V2; 25 import static android.os.Process.myUserHandle; 26 27 import static com.android.bedstead.nene.users.UserType.MANAGED_PROFILE_TYPE_NAME; 28 import static com.android.bedstead.nene.users.UserType.SECONDARY_USER_TYPE_NAME; 29 import static com.android.bedstead.nene.users.UserType.SYSTEM_USER_TYPE_NAME; 30 31 import android.app.ActivityManager; 32 import android.content.Context; 33 import android.content.pm.UserInfo; 34 import android.os.Build; 35 import android.os.UserHandle; 36 import android.os.UserManager; 37 38 import androidx.annotation.CheckResult; 39 import androidx.annotation.Nullable; 40 41 import com.android.bedstead.nene.TestApis; 42 import com.android.bedstead.nene.exceptions.AdbException; 43 import com.android.bedstead.nene.exceptions.AdbParseException; 44 import com.android.bedstead.nene.exceptions.NeneException; 45 import com.android.bedstead.nene.permissions.PermissionContext; 46 import com.android.bedstead.nene.permissions.Permissions; 47 import com.android.bedstead.nene.utils.Poll; 48 import com.android.bedstead.nene.utils.ShellCommand; 49 import com.android.bedstead.nene.utils.Versions; 50 51 import java.time.Duration; 52 import java.util.ArrayList; 53 import java.util.Collection; 54 import java.util.Comparator; 55 import java.util.HashMap; 56 import java.util.HashSet; 57 import java.util.Iterator; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Set; 61 import java.util.function.Function; 62 import java.util.stream.Collectors; 63 import java.util.stream.Stream; 64 65 public final class Users { 66 67 static final int SYSTEM_USER_ID = 0; 68 private static final Duration WAIT_FOR_USER_TIMEOUT = Duration.ofMinutes(4); 69 70 private Map<Integer, AdbUser> mCachedUsers = null; 71 private Map<String, UserType> mCachedUserTypes = null; 72 private Set<UserType> mCachedUserTypeValues = null; 73 private final AdbUserParser mParser; 74 private static final UserManager sUserManager = 75 TestApis.context().instrumentedContext().getSystemService(UserManager.class); 76 77 public static final Users sInstance = new Users(); 78 Users()79 private Users() { 80 mParser = AdbUserParser.get(SDK_INT); 81 } 82 83 /** Get all {@link UserReference}s on the device. */ all()84 public Collection<UserReference> all() { 85 if (!Versions.meetsMinimumSdkVersionRequirement(S)) { 86 fillCache(); 87 return mCachedUsers.keySet().stream().map(UserReference::new) 88 .collect(Collectors.toSet()); 89 } 90 91 return users().map( 92 ui -> new UserReference(ui.id) 93 ).collect(Collectors.toSet()); 94 } 95 96 /** 97 * Gets a {@link UserReference} for the initial user for the device. 98 * 99 * <p>This will be the {@link #system()} user on most systems.</p> 100 */ initial()101 public UserReference initial() { 102 if (!isHeadlessSystemUserMode()) { 103 return system(); 104 } 105 if (TestApis.packages().features().contains("android.hardware.type.automotive")) { 106 try { 107 return ShellCommand.builder("cmd car_service get-initial-user") 108 .executeAndParseOutput(i -> find(Integer.parseInt(i.trim()))); 109 } catch (AdbException e) { 110 throw new NeneException("Error finding initial user on Auto", e); 111 } 112 } 113 114 List<UserReference> users = new ArrayList<>(all()); 115 users.sort(Comparator.comparingInt(UserReference::id)); 116 117 for (UserReference user : users) { 118 if (user.parent() == null) { 119 return user; 120 } 121 } 122 123 throw new NeneException("No initial user available"); 124 } 125 126 /** Get a {@link UserReference} for the user currently switched to. */ current()127 public UserReference current() { 128 if (Versions.meetsMinimumSdkVersionRequirement(S)) { 129 try (PermissionContext p = 130 TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) { 131 return find(ActivityManager.getCurrentUser()); 132 } 133 } 134 135 try { 136 return find((int) ShellCommand.builder("am get-current-user") 137 .executeAndParseOutput(i -> Integer.parseInt(i.trim()))); 138 } catch (AdbException e) { 139 throw new NeneException("Error getting current user", e); 140 } 141 } 142 143 /** Get a {@link UserReference} for the user running the current test process. */ instrumented()144 public UserReference instrumented() { 145 return find(myUserHandle()); 146 } 147 148 /** Get a {@link UserReference} for the system user. */ system()149 public UserReference system() { 150 return find(0); 151 } 152 153 /** Get a {@link UserReference} by {@code id}. */ find(int id)154 public UserReference find(int id) { 155 return new UserReference(id); 156 } 157 158 /** Get a {@link UserReference} by {@code userHandle}. */ find(UserHandle userHandle)159 public UserReference find(UserHandle userHandle) { 160 return new UserReference(userHandle.getIdentifier()); 161 } 162 163 /** Get all supported {@link UserType}s. */ supportedTypes()164 public Set<UserType> supportedTypes() { 165 // TODO(b/203557600): Stop using adb 166 ensureSupportedTypesCacheFilled(); 167 return mCachedUserTypeValues; 168 } 169 170 /** Get a {@link UserType} with the given {@code typeName}, or {@code null} */ 171 @Nullable supportedType(String typeName)172 public UserType supportedType(String typeName) { 173 ensureSupportedTypesCacheFilled(); 174 return mCachedUserTypes.get(typeName); 175 } 176 177 /** 178 * Find all users which have the given {@link UserType}. 179 */ findUsersOfType(UserType userType)180 public Set<UserReference> findUsersOfType(UserType userType) { 181 if (userType == null) { 182 throw new NullPointerException(); 183 } 184 185 if (userType.baseType().contains(UserType.BaseType.PROFILE)) { 186 throw new NeneException("Cannot use findUsersOfType with profile type " + userType); 187 } 188 189 return all().stream() 190 .filter(u -> u.type().equals(userType)) 191 .collect(Collectors.toSet()); 192 } 193 194 /** 195 * Find a single user which has the given {@link UserType}. 196 * 197 * <p>If there are no users of the given type, {@code Null} will be returned. 198 * 199 * <p>If there is more than one user of the given type, {@link NeneException} will be thrown. 200 */ 201 @Nullable findUserOfType(UserType userType)202 public UserReference findUserOfType(UserType userType) { 203 Set<UserReference> users = findUsersOfType(userType); 204 205 if (users.isEmpty()) { 206 return null; 207 } else if (users.size() > 1) { 208 throw new NeneException("findUserOfType called but there is more than 1 user of type " 209 + userType + ". Found: " + users); 210 } 211 212 return users.iterator().next(); 213 } 214 215 /** 216 * Find all users which have the given {@link UserType} and the given parent. 217 */ findProfilesOfType(UserType userType, UserReference parent)218 public Set<UserReference> findProfilesOfType(UserType userType, UserReference parent) { 219 if (userType == null || parent == null) { 220 throw new NullPointerException(); 221 } 222 223 if (!userType.baseType().contains(UserType.BaseType.PROFILE)) { 224 throw new NeneException("Cannot use findProfilesOfType with non-profile type " 225 + userType); 226 } 227 228 return all().stream() 229 .filter(u -> parent.equals(u.parent()) 230 && u.type().equals(userType)) 231 .collect(Collectors.toSet()); 232 } 233 234 /** 235 * Find all users which have the given {@link UserType} and the given parent. 236 * 237 * <p>If there are no users of the given type and parent, {@code Null} will be returned. 238 * 239 * <p>If there is more than one user of the given type and parent, {@link NeneException} will 240 * be thrown. 241 */ 242 @Nullable findProfileOfType(UserType userType, UserReference parent)243 public UserReference findProfileOfType(UserType userType, UserReference parent) { 244 Set<UserReference> profiles = findProfilesOfType(userType, parent); 245 246 if (profiles.isEmpty()) { 247 return null; 248 } else if (profiles.size() > 1) { 249 throw new NeneException("findProfileOfType called but there is more than 1 user of " 250 + "type " + userType + " with parent " + parent + ". Found: " + profiles); 251 } 252 253 return profiles.iterator().next(); 254 } 255 ensureSupportedTypesCacheFilled()256 private void ensureSupportedTypesCacheFilled() { 257 if (mCachedUserTypes != null) { 258 // SupportedTypes don't change so don't need to be refreshed 259 return; 260 } 261 if (SDK_INT < Build.VERSION_CODES.R) { 262 mCachedUserTypes = new HashMap<>(); 263 mCachedUserTypes.put(MANAGED_PROFILE_TYPE_NAME, managedProfileUserType()); 264 mCachedUserTypes.put(SYSTEM_USER_TYPE_NAME, systemUserType()); 265 mCachedUserTypes.put(SECONDARY_USER_TYPE_NAME, secondaryUserType()); 266 mCachedUserTypeValues = new HashSet<>(); 267 mCachedUserTypeValues.addAll(mCachedUserTypes.values()); 268 return; 269 } 270 271 fillCache(); 272 } 273 managedProfileUserType()274 private UserType managedProfileUserType() { 275 UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType(); 276 managedProfileMutableUserType.mName = MANAGED_PROFILE_TYPE_NAME; 277 managedProfileMutableUserType.mBaseType = Set.of(UserType.BaseType.PROFILE); 278 managedProfileMutableUserType.mEnabled = true; 279 managedProfileMutableUserType.mMaxAllowed = -1; 280 managedProfileMutableUserType.mMaxAllowedPerParent = 1; 281 return new UserType(managedProfileMutableUserType); 282 } 283 systemUserType()284 private UserType systemUserType() { 285 UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType(); 286 managedProfileMutableUserType.mName = SYSTEM_USER_TYPE_NAME; 287 managedProfileMutableUserType.mBaseType = 288 Set.of(UserType.BaseType.FULL, UserType.BaseType.SYSTEM); 289 managedProfileMutableUserType.mEnabled = true; 290 managedProfileMutableUserType.mMaxAllowed = -1; 291 managedProfileMutableUserType.mMaxAllowedPerParent = -1; 292 return new UserType(managedProfileMutableUserType); 293 } 294 secondaryUserType()295 private UserType secondaryUserType() { 296 UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType(); 297 managedProfileMutableUserType.mName = SECONDARY_USER_TYPE_NAME; 298 managedProfileMutableUserType.mBaseType = Set.of(UserType.BaseType.FULL); 299 managedProfileMutableUserType.mEnabled = true; 300 managedProfileMutableUserType.mMaxAllowed = -1; 301 managedProfileMutableUserType.mMaxAllowedPerParent = -1; 302 return new UserType(managedProfileMutableUserType); 303 } 304 305 /** 306 * Create a new user. 307 */ 308 @CheckResult createUser()309 public UserBuilder createUser() { 310 return new UserBuilder(); 311 } 312 313 /** 314 * Get a {@link UserReference} to a user who does not exist. 315 */ nonExisting()316 public UserReference nonExisting() { 317 Set<Integer> userIds; 318 if (Versions.meetsMinimumSdkVersionRequirement(S)) { 319 userIds = users().map(ui -> ui.id).collect(Collectors.toSet()); 320 } else { 321 fillCache(); 322 userIds = mCachedUsers.keySet(); 323 } 324 325 int id = 0; 326 327 while (userIds.contains(id)) { 328 id++; 329 } 330 331 return new UserReference(id); 332 } 333 fillCache()334 private void fillCache() { 335 try { 336 // TODO: Replace use of adb on supported versions of Android 337 String userDumpsysOutput = ShellCommand.builder("dumpsys user").execute(); 338 AdbUserParser.ParseResult result = mParser.parse(userDumpsysOutput); 339 340 mCachedUsers = result.mUsers; 341 if (result.mUserTypes != null) { 342 mCachedUserTypes = result.mUserTypes; 343 } else { 344 ensureSupportedTypesCacheFilled(); 345 } 346 347 Iterator<Map.Entry<Integer, AdbUser>> iterator = mCachedUsers.entrySet().iterator(); 348 349 while (iterator.hasNext()) { 350 Map.Entry<Integer, AdbUser> entry = iterator.next(); 351 352 if (entry.getValue().isRemoving()) { 353 // We don't expose users who are currently being removed 354 iterator.remove(); 355 continue; 356 } 357 358 AdbUser.MutableUser mutableUser = entry.getValue().mMutableUser; 359 360 if (SDK_INT < Build.VERSION_CODES.R) { 361 if (entry.getValue().id() == SYSTEM_USER_ID) { 362 mutableUser.mType = supportedType(SYSTEM_USER_TYPE_NAME); 363 mutableUser.mIsPrimary = true; 364 } else if (entry.getValue().hasFlag(AdbUser.FLAG_MANAGED_PROFILE)) { 365 mutableUser.mType = 366 supportedType(MANAGED_PROFILE_TYPE_NAME); 367 mutableUser.mIsPrimary = false; 368 } else { 369 mutableUser.mType = 370 supportedType(SECONDARY_USER_TYPE_NAME); 371 mutableUser.mIsPrimary = false; 372 } 373 } 374 375 if (SDK_INT < S) { 376 if (mutableUser.mType.baseType() 377 .contains(UserType.BaseType.PROFILE)) { 378 // We assume that all profiles before S were on the System User 379 mutableUser.mParent = find(SYSTEM_USER_ID); 380 } 381 } 382 } 383 384 mCachedUserTypeValues = new HashSet<>(); 385 mCachedUserTypeValues.addAll(mCachedUserTypes.values()); 386 387 } catch (AdbException | AdbParseException e) { 388 throw new RuntimeException("Error filling cache", e); 389 } 390 } 391 392 /** 393 * Block until the user with the given {@code userReference} to not exist or to be in the 394 * correct state. 395 * 396 * <p>If this cannot be met before a timeout, a {@link NeneException} will be thrown. 397 */ 398 @Nullable waitForUserToNotExistOrMatch( UserReference userReference, Function<UserReference, Boolean> userChecker)399 UserReference waitForUserToNotExistOrMatch( 400 UserReference userReference, Function<UserReference, Boolean> userChecker) { 401 return waitForUserToMatch(userReference, userChecker, /* waitForExist= */ false); 402 } 403 404 @Nullable waitForUserToMatch( UserReference userReference, Function<UserReference, Boolean> userChecker, boolean waitForExist)405 private UserReference waitForUserToMatch( 406 UserReference userReference, Function<UserReference, Boolean> userChecker, 407 boolean waitForExist) { 408 // TODO(scottjonathan): This is pretty heavy because we resolve everything when we know we 409 // are throwing away everything except one user. Optimise 410 try { 411 return Poll.forValue("user", () -> userReference) 412 .toMeet((user) -> { 413 if (user == null) { 414 return !waitForExist; 415 } 416 return userChecker.apply(user); 417 }).timeout(WAIT_FOR_USER_TIMEOUT) 418 .errorOnFail("Expected user to meet requirement") 419 .await(); 420 } catch (AssertionError e) { 421 if (!userReference.exists()) { 422 throw new NeneException( 423 "Timed out waiting for user state for user " 424 + userReference + ". User does not exist.", e); 425 } 426 throw new NeneException( 427 "Timed out waiting for user state, current state " + userReference, e 428 ); 429 } 430 } 431 432 /** See {@link UserManager#isHeadlessSystemUserMode()}. */ 433 @SuppressWarnings("NewApi") 434 public boolean isHeadlessSystemUserMode() { 435 if (Versions.meetsMinimumSdkVersionRequirement(S)) { 436 return UserManager.isHeadlessSystemUserMode(); 437 } 438 439 return false; 440 } 441 442 /** 443 * Set the stopBgUsersOnSwitch property. 444 * 445 * <p>This affects if background users will be swapped when switched away from on some devices. 446 */ 447 public void setStopBgUsersOnSwitch(int value) { 448 if (!Versions.meetsMinimumSdkVersionRequirement(S_V2)) { 449 return; 450 } 451 Context context = TestApis.context().instrumentedContext(); 452 try (PermissionContext p = TestApis.permissions() 453 .withPermission(INTERACT_ACROSS_USERS)) { 454 context.getSystemService(ActivityManager.class).setStopUserOnSwitch(value); 455 } 456 } 457 458 @Nullable 459 AdbUser fetchUser(int id) { 460 fillCache(); 461 return mCachedUsers.get(id); 462 } 463 464 static Stream<UserInfo> users() { 465 if (Permissions.sIgnorePermissions.get()) { 466 return sUserManager.getUsers( 467 /* excludePartial= */ false, 468 /* excludeDying= */ true, 469 /* excludePreCreated= */ false).stream(); 470 } 471 472 try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) { 473 return sUserManager.getUsers( 474 /* excludePartial= */ false, 475 /* excludeDying= */ true, 476 /* excludePreCreated= */ false).stream(); 477 } 478 } 479 } 480