• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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