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.os.Build.VERSION.SDK_INT;
20 import static android.os.Process.myUserHandle;
21 
22 import static com.android.bedstead.nene.users.UserType.MANAGED_PROFILE_TYPE_NAME;
23 import static com.android.bedstead.nene.users.UserType.SECONDARY_USER_TYPE_NAME;
24 import static com.android.bedstead.nene.users.UserType.SYSTEM_USER_TYPE_NAME;
25 
26 import android.os.Build;
27 import android.os.UserHandle;
28 
29 import androidx.annotation.CheckResult;
30 import androidx.annotation.Nullable;
31 
32 import com.android.bedstead.nene.TestApis;
33 import com.android.bedstead.nene.exceptions.AdbException;
34 import com.android.bedstead.nene.exceptions.AdbParseException;
35 import com.android.bedstead.nene.exceptions.NeneException;
36 import com.android.bedstead.nene.utils.ShellCommand;
37 import com.android.compatibility.common.util.PollingCheck;
38 
39 import java.util.Collection;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import java.util.Iterator;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.concurrent.atomic.AtomicReference;
46 import java.util.function.Function;
47 import java.util.stream.Collectors;
48 
49 public final class Users {
50 
51     static final int SYSTEM_USER_ID = 0;
52     private static final long WAIT_FOR_USER_TIMEOUT_MS = 1000 * 60;
53 
54     private Map<Integer, User> mCachedUsers = null;
55     private Map<String, UserType> mCachedUserTypes = null;
56     private Set<UserType> mCachedUserTypeValues = null;
57     private final AdbUserParser mParser;
58     private final TestApis mTestApis;
59 
Users(TestApis testApis)60     public Users(TestApis testApis) {
61         mTestApis = testApis;
62         mParser = AdbUserParser.get(mTestApis, SDK_INT);
63     }
64 
65     /** Get all {@link User}s on the device. */
all()66     public Collection<User> all() {
67         fillCache();
68 
69         return mCachedUsers.values();
70     }
71 
72     /** Get a {@link UserReference} for the user running the current test process. */
instrumented()73     public UserReference instrumented() {
74         return find(myUserHandle());
75     }
76 
77     /** Get a {@link UserReference} for the system user. */
system()78     public UserReference system() {
79         return find(0);
80     }
81 
82     /** Get a {@link UserReference} by {@code id}. */
find(int id)83     public UserReference find(int id) {
84         return new UnresolvedUser(mTestApis, id);
85     }
86 
87     /** Get a {@link UserReference} by {@code userHandle}. */
find(UserHandle userHandle)88     public UserReference find(UserHandle userHandle) {
89         return new UnresolvedUser(mTestApis, userHandle.getIdentifier());
90     }
91 
92     @Nullable
fetchUser(int id)93     User fetchUser(int id) {
94         // TODO(scottjonathan): fillCache probably does more than we need here -
95         //  can we make it more efficient?
96         fillCache();
97 
98         return mCachedUsers.get(id);
99     }
100 
101     /** Get all supported {@link UserType}s. */
supportedTypes()102     public Set<UserType> supportedTypes() {
103         ensureSupportedTypesCacheFilled();
104         return mCachedUserTypeValues;
105     }
106 
107     /** Get a {@link UserType} with the given {@code typeName}, or {@code null} */
supportedType(String typeName)108     public UserType supportedType(String typeName) {
109         ensureSupportedTypesCacheFilled();
110         return mCachedUserTypes.get(typeName);
111     }
112 
113     /**
114      * Find all users which have the given {@link UserType}.
115      */
findUsersOfType(UserType userType)116     public Set<UserReference> findUsersOfType(UserType userType) {
117         if (userType == null) {
118             throw new NullPointerException();
119         }
120 
121         if (userType.baseType().contains(UserType.BaseType.PROFILE)) {
122             throw new NeneException("Cannot use findUsersOfType with profile type " + userType);
123         }
124 
125         return all().stream()
126                 .filter(u -> u.type().equals(userType))
127                 .collect(Collectors.toSet());
128     }
129 
130     /**
131      * Find a single user which has the given {@link UserType}.
132      *
133      * <p>If there are no users of the given type, {@code Null} will be returned.
134      *
135      * <p>If there is more than one user of the given type, {@link NeneException} will be thrown.
136      */
137     @Nullable
findUserOfType(UserType userType)138     public UserReference findUserOfType(UserType userType) {
139         Set<UserReference> users = findUsersOfType(userType);
140 
141         if (users.isEmpty()) {
142             return null;
143         } else if (users.size() > 1) {
144             throw new NeneException("findUserOfType called but there is more than 1 user of type "
145                     + userType + ". Found: " + users);
146         }
147 
148         return users.iterator().next();
149     }
150 
151     /**
152      * Find all users which have the given {@link UserType} and the given parent.
153      */
findProfilesOfType(UserType userType, UserReference parent)154     public Set<UserReference> findProfilesOfType(UserType userType, UserReference parent) {
155         if (userType == null || parent == null) {
156             throw new NullPointerException();
157         }
158 
159         if (!userType.baseType().contains(UserType.BaseType.PROFILE)) {
160             throw new NeneException("Cannot use findProfilesOfType with non-profile type "
161                     + userType);
162         }
163 
164         return all().stream()
165                 .filter(u -> parent.equals(u.parent())
166                         && u.type().equals(userType))
167                 .collect(Collectors.toSet());
168     }
169 
170     /**
171      * Find all users which have the given {@link UserType} and the given parent.
172      *
173      * <p>If there are no users of the given type and parent, {@code Null} will be returned.
174      *
175      * <p>If there is more than one user of the given type and parent, {@link NeneException} will
176      * be thrown.
177      */
178     @Nullable
findProfileOfType(UserType userType, UserReference parent)179     public UserReference findProfileOfType(UserType userType, UserReference parent) {
180         Set<UserReference> profiles = findProfilesOfType(userType, parent);
181 
182         if (profiles.isEmpty()) {
183             return null;
184         } else if (profiles.size() > 1) {
185             throw new NeneException("findProfileOfType called but there is more than 1 user of "
186                     + "type " + userType + " with parent " + parent + ". Found: " + profiles);
187         }
188 
189         return profiles.iterator().next();
190     }
191 
ensureSupportedTypesCacheFilled()192     private void ensureSupportedTypesCacheFilled() {
193         if (mCachedUserTypes != null) {
194             // SupportedTypes don't change so don't need to be refreshed
195             return;
196         }
197         if (SDK_INT < Build.VERSION_CODES.R) {
198             mCachedUserTypes = new HashMap<>();
199             mCachedUserTypes.put(MANAGED_PROFILE_TYPE_NAME, managedProfileUserType());
200             mCachedUserTypes.put(SYSTEM_USER_TYPE_NAME, systemUserType());
201             mCachedUserTypes.put(SECONDARY_USER_TYPE_NAME, secondaryUserType());
202             mCachedUserTypeValues = new HashSet<>();
203             mCachedUserTypeValues.addAll(mCachedUserTypes.values());
204             return;
205         }
206 
207         fillCache();
208     }
209 
managedProfileUserType()210     private UserType managedProfileUserType() {
211         UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType();
212         managedProfileMutableUserType.mName = MANAGED_PROFILE_TYPE_NAME;
213         managedProfileMutableUserType.mBaseType = Set.of(UserType.BaseType.PROFILE);
214         managedProfileMutableUserType.mEnabled = true;
215         managedProfileMutableUserType.mMaxAllowed = -1;
216         managedProfileMutableUserType.mMaxAllowedPerParent = 1;
217         return new UserType(managedProfileMutableUserType);
218     }
219 
systemUserType()220     private UserType systemUserType() {
221         UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType();
222         managedProfileMutableUserType.mName = SYSTEM_USER_TYPE_NAME;
223         managedProfileMutableUserType.mBaseType =
224                 Set.of(UserType.BaseType.FULL, UserType.BaseType.SYSTEM);
225         managedProfileMutableUserType.mEnabled = true;
226         managedProfileMutableUserType.mMaxAllowed = -1;
227         managedProfileMutableUserType.mMaxAllowedPerParent = -1;
228         return new UserType(managedProfileMutableUserType);
229     }
230 
secondaryUserType()231     private UserType secondaryUserType() {
232         UserType.MutableUserType managedProfileMutableUserType = new UserType.MutableUserType();
233         managedProfileMutableUserType.mName = SECONDARY_USER_TYPE_NAME;
234         managedProfileMutableUserType.mBaseType = Set.of(UserType.BaseType.FULL);
235         managedProfileMutableUserType.mEnabled = true;
236         managedProfileMutableUserType.mMaxAllowed = -1;
237         managedProfileMutableUserType.mMaxAllowedPerParent = -1;
238         return new UserType(managedProfileMutableUserType);
239     }
240 
241     /**
242      * Create a new user.
243      */
244     @CheckResult
createUser()245     public UserBuilder createUser() {
246         return new UserBuilder(mTestApis);
247     }
248 
249     /**
250      * Get a {@link UserReference} to a user who does not exist.
251      */
nonExisting()252     public UserReference nonExisting() {
253         fillCache();
254         int id = 0;
255 
256         while (mCachedUsers.get(id) != null) {
257             id++;
258         }
259 
260         return new UnresolvedUser(mTestApis, id);
261     }
262 
fillCache()263     private void fillCache() {
264         try {
265             // TODO: Replace use of adb on supported versions of Android
266             String userDumpsysOutput = ShellCommand.builder("dumpsys user").execute();
267             AdbUserParser.ParseResult result = mParser.parse(userDumpsysOutput);
268 
269             mCachedUsers = result.mUsers;
270             if (result.mUserTypes != null) {
271                 mCachedUserTypes = result.mUserTypes;
272             } else {
273                 ensureSupportedTypesCacheFilled();
274             }
275 
276             Iterator<Map.Entry<Integer, User>> iterator = mCachedUsers.entrySet().iterator();
277 
278             while (iterator.hasNext()) {
279                 Map.Entry<Integer, User> entry = iterator.next();
280 
281                 if (entry.getValue().isRemoving()) {
282                     // We don't expose users who are currently being removed
283                     iterator.remove();
284                     continue;
285                 }
286 
287                 User.MutableUser mutableUser = entry.getValue().mMutableUser;
288 
289                 if (SDK_INT < Build.VERSION_CODES.R) {
290                     if (entry.getValue().id() == SYSTEM_USER_ID) {
291                         mutableUser.mType = supportedType(SYSTEM_USER_TYPE_NAME);
292                         mutableUser.mIsPrimary = true;
293                     } else if (entry.getValue().hasFlag(User.FLAG_MANAGED_PROFILE)) {
294                         mutableUser.mType =
295                                 supportedType(MANAGED_PROFILE_TYPE_NAME);
296                         mutableUser.mIsPrimary = false;
297                     } else {
298                         mutableUser.mType =
299                                 supportedType(SECONDARY_USER_TYPE_NAME);
300                         mutableUser.mIsPrimary = false;
301                     }
302                 }
303 
304                 if (SDK_INT < Build.VERSION_CODES.S) {
305                     if (mutableUser.mType.baseType()
306                             .contains(UserType.BaseType.PROFILE)) {
307                         // We assume that all profiles before S were on the System User
308                         mutableUser.mParent = find(SYSTEM_USER_ID);
309                     }
310                 }
311             }
312 
313             mCachedUserTypeValues = new HashSet<>();
314             mCachedUserTypeValues.addAll(mCachedUserTypes.values());
315 
316         } catch (AdbException | AdbParseException e) {
317             throw new RuntimeException("Error filling cache", e);
318         }
319     }
320 
321     /**
322      * Block until the user with the given {@code userReference} exists and is in the correct state.
323      *
324      * <p>If this cannot be met before a timeout, a {@link NeneException} will be thrown.
325      */
waitForUserToMatch(UserReference userReference, Function<User, Boolean> userChecker)326     User waitForUserToMatch(UserReference userReference, Function<User, Boolean> userChecker) {
327         return waitForUserToMatch(userReference, userChecker, /* waitForExist= */ true);
328     }
329 
330     /**
331      * Block until the user with the given {@code userReference} to not exist or to be in the
332      * correct state.
333      *
334      * <p>If this cannot be met before a timeout, a {@link NeneException} will be thrown.
335      */
336     @Nullable
waitForUserToNotExistOrMatch( UserReference userReference, Function<User, Boolean> userChecker)337     User waitForUserToNotExistOrMatch(
338             UserReference userReference, Function<User, Boolean> userChecker) {
339         return waitForUserToMatch(userReference, userChecker, /* waitForExist= */ false);
340     }
341 
342     @Nullable
waitForUserToMatch( UserReference userReference, Function<User, Boolean> userChecker, boolean waitForExist)343     private User waitForUserToMatch(
344             UserReference userReference, Function<User, Boolean> userChecker,
345             boolean waitForExist) {
346         // TODO(scottjonathan): This is pretty heavy because we resolve everything when we know we
347         //  are throwing away everything except one user. Optimise
348         try {
349             AtomicReference<User> returnUser = new AtomicReference<>();
350             PollingCheck.waitFor(WAIT_FOR_USER_TIMEOUT_MS, () -> {
351                 User user = userReference.resolve();
352                 returnUser.set(user);
353                 if (user == null) {
354                     return !waitForExist;
355                 }
356                 return userChecker.apply(user);
357             });
358             return returnUser.get();
359         } catch (AssertionError e) {
360             User user = userReference.resolve();
361 
362             if (user == null) {
363                 throw new NeneException(
364                         "Timed out waiting for user state for user "
365                                 + userReference + ". User does not exist.", e);
366             }
367             throw new NeneException(
368                     "Timed out waiting for user state, current state " + user, e
369             );
370         }
371     }
372 }
373