• 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.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