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