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