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