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