1 /*
2  * Copyright (C) 2020 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 package android.car.test.util;
17 
18 import static android.car.PlatformVersion.VERSION_CODES.UPSIDE_DOWN_CAKE_0;
19 
20 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
21 
22 import static org.junit.Assume.assumeTrue;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.UserIdInt;
27 import android.app.ActivityManager;
28 import android.car.Car;
29 import android.car.CarOccupantZoneManager;
30 import android.content.Context;
31 import android.content.pm.UserInfo;
32 import android.content.pm.UserInfo.UserInfoFlag;
33 import android.os.UserHandle;
34 import android.os.UserManager;
35 import android.util.Log;
36 
37 import com.android.internal.util.Preconditions;
38 
39 import org.junit.AssumptionViolatedException;
40 
41 import java.util.Arrays;
42 import java.util.List;
43 import java.util.stream.Collectors;
44 
45 /**
46  * Provides utilities for Android User related tasks.
47  */
48 public final class UserTestingHelper {
49 
50     private static final String TAG = UserTestingHelper.class.getSimpleName();
51 
52     /**
53      * Checks if the target device supports MUMD (multi-user multi-display).
54      * @throws AssumptionViolatedException if the device does not support MUMD.
55      */
56     // TODO(b/250108245): Currently doing this because using DeviceState rule is very heavy. We
57     //  may want to use PermissionsCheckerRule as a light-weight feature check
58     //  (and probably rename it to something like DeviceStateLite).
requireMumd(Context context)59     public static void requireMumd(Context context) {
60         assumeTrue(
61                 "The device does not support multiple users on multiple displays",
62                 Car.getPlatformVersion().isAtLeast(UPSIDE_DOWN_CAKE_0)
63                 && context.getSystemService(UserManager.class).isVisibleBackgroundUsersSupported());
64     }
65 
66     /**
67      * Returns a display that is available to start a background user on.
68      *
69      * @return the id of a secondary display that is not assigned to any user, if any.
70      * @throws IllegalStateException when there is no secondary display available.
71      */
getDisplayForStartingBackgroundUser( Context context, CarOccupantZoneManager occupantZoneManager)72     public static int getDisplayForStartingBackgroundUser(
73             Context context, CarOccupantZoneManager occupantZoneManager) {
74         int[] displayIds = context.getSystemService(ActivityManager.class)
75                 .getDisplayIdsForStartingVisibleBackgroundUsers();
76         Log.d(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers() display IDs"
77                 + " returned by AM: " + Arrays.toString(displayIds));
78         if (displayIds == null || displayIds.length == 0) {
79             throw new IllegalStateException("No secondary display is available to start a user.");
80         }
81 
82         for (int displayId : displayIds) {
83             int userId = occupantZoneManager.getUserForDisplayId(displayId);
84             if (userId == CarOccupantZoneManager.INVALID_USER_ID) {
85                 Log.d(TAG, "Returning first available display: " + displayId);
86                 return displayId;
87             }
88             Log.d(TAG, "Display " + displayId + "is curretnly assigned to user " + userId);
89         }
90 
91         throw new IllegalStateException(
92                 "All secondary displays are assigned. No secondary display is available.");
93     }
94 
95     /**
96      * Creates a simple {@link UserInfo}, containing just the given {@code userId}.
97      */
98     @NonNull
newUser(@serIdInt int userId)99     public static UserInfo newUser(@UserIdInt int userId) {
100         return new UserInfoBuilder(userId).build();
101     }
102 
103     /**
104      * Creates a simple {@link UserInfo}, containing just the given {@code userId}
105      * and {@code userName}.
106      */
107     @NonNull
newUser(@serIdInt int userId, @NonNull String userName)108     public static UserInfo newUser(@UserIdInt int userId, @NonNull String userName) {
109         return new UserInfoBuilder(userId).setName(userName).build();
110     }
111 
112     /**
113      * Creates a list of {@link UserInfo UserInfos}, each containing just the given user ids.
114      */
115     @NonNull
newUsers(@serIdInt int... userIds)116     public static List<UserInfo> newUsers(@UserIdInt int... userIds) {
117         return Arrays.stream(userIds)
118                 .mapToObj(id -> newUser(id))
119                 .collect(Collectors.toList());
120     }
121 
122     /**
123      * Creates a list of {@link UserHandle UserHandles}, each containing just the given user ids.
124      */
125     @NonNull
newUserHandles(@serIdInt int... userIds)126     public static List<UserHandle> newUserHandles(@UserIdInt int... userIds) {
127         return Arrays.stream(userIds)
128                 .mapToObj(id -> UserHandle.of(id))
129                 .collect(Collectors.toList());
130     }
131 
132     /**
133      * Creates a list of {@link UserInfo UserInfos}.
134      */
135     @NonNull
toList(@onNull UserInfo... users)136     public static List<UserInfo> toList(@NonNull UserInfo... users) {
137         return Arrays.stream(users).collect(Collectors.toList());
138     }
139 
140     /**
141      * Creates a list of {@link UserHandle UserHandles}.
142      */
143     @NonNull
toList(@onNull UserHandle... users)144     public static List<UserHandle> toList(@NonNull UserHandle... users) {
145         return Arrays.stream(users).collect(Collectors.toList());
146     }
147 
148     /**
149      * Creates a {@link UserInfo} with the type explicitly set and with the given {@code userId}.
150      */
151     @NonNull
newSecondaryUser(@serIdInt int userId)152     public static UserInfo newSecondaryUser(@UserIdInt int userId) {
153         return new UserInfoBuilder(userId).setType(UserManager.USER_TYPE_FULL_SECONDARY).build();
154     }
155 
156     /**
157      * Creates a new guest with the given {@code userId} and proper flags and types set.
158      */
159     @NonNull
newGuestUser(@serIdInt int userId, boolean ephemeral)160     public static UserInfo newGuestUser(@UserIdInt int userId, boolean ephemeral) {
161         return new UserInfoBuilder(userId).setGuest(true).setEphemeral(ephemeral).build();
162     }
163 
164     /**
165      * Creates a new guest with the given {@code userId} and without any flag..
166      */
167     @NonNull
newGuestUser(@serIdInt int userId)168     public static UserInfo newGuestUser(@UserIdInt int userId) {
169         return new UserInfoBuilder(userId).setGuest(true).build();
170     }
171 
172     /**
173      * Gets the default {@link UserInfo#userType} for a guest / regular user.
174      */
175     @NonNull
getDefaultUserType(boolean isGuest)176     public static String getDefaultUserType(boolean isGuest) {
177         return isGuest ? UserManager.USER_TYPE_FULL_GUEST : UserManager.USER_TYPE_FULL_SECONDARY;
178     }
179 
180     /**
181      * Sets the property that defines the maximum number of uses allowed in the device.
182      */
setMaxSupportedUsers(int max)183     public static void setMaxSupportedUsers(int max) {
184         runShellCommand("setprop fw.max_users %d", max);
185     }
186 
187     /**
188      * Configures the user to use PIN credentials.
189      */
setUserLockCredentials(@serIdInt int userId, int pin)190     public static void setUserLockCredentials(@UserIdInt int userId, int pin) {
191         runShellCommand("locksettings set-pin %s --user %d ", pin, userId);
192     }
193 
194     /**
195      * Clears the user credentials using current PIN.
196      */
clearUserLockCredentials(@serIdInt int userId, int pin)197     public static void clearUserLockCredentials(@UserIdInt int userId, int pin) {
198         runShellCommand("locksettings clear --old %d --user %d ", pin, userId);
199     }
200 
201     /**
202      * Builder for {@link UserInfo} objects.
203      */
204     public static final class UserInfoBuilder {
205 
206         @UserIdInt
207         private final int mUserId;
208 
209         @UserInfoFlag
210         private int mFlags;
211 
212         @Nullable
213         private String mName;
214 
215         @Nullable
216         private String mType;
217 
218         private boolean mGuest;
219         private boolean mEphemeral;
220         private boolean mAdmin;
221         private boolean mInitialized;
222 
223         /**
224          * Default constructor.
225          */
UserInfoBuilder(@serIdInt int userId)226         public UserInfoBuilder(@UserIdInt int userId) {
227             mUserId = userId;
228         }
229 
230         /**
231          * Sets the user name.
232          */
233         @NonNull
setName(@ullable String name)234         public UserInfoBuilder setName(@Nullable String name) {
235             mName = name;
236             return this;
237         }
238 
239         /**
240          * Sets the user type.
241          */
242         @NonNull
setType(@ullable String type)243         public UserInfoBuilder setType(@Nullable String type) {
244             Preconditions.checkState(!mGuest, "cannot set type (" + mType + ") after setting it as "
245                     + "guest");
246             mType = type;
247             return this;
248         }
249 
250         /**
251          * Sets whether the user is a guest.
252          */
253         @NonNull
setGuest(boolean guest)254         public UserInfoBuilder setGuest(boolean guest) {
255             Preconditions.checkState(mType == null, "cannot set guest after setting type (" + mType
256                     + ")");
257             mGuest = guest;
258             return this;
259         }
260 
261         /**
262          * Sets the user flags
263          */
264         @NonNull
setFlags(@serInfoFlag int flags)265         public UserInfoBuilder setFlags(@UserInfoFlag int flags) {
266             mFlags = flags;
267             return this;
268         }
269 
270         /**
271          * Sets whether the user is ephemeral.
272          */
273         @NonNull
setEphemeral(boolean ephemeral)274         public UserInfoBuilder setEphemeral(boolean ephemeral) {
275             mEphemeral = ephemeral;
276             return this;
277         }
278 
279         /**
280          * Sets whether the user is an admin.
281          */
282         @NonNull
setAdmin(boolean admin)283         public UserInfoBuilder setAdmin(boolean admin) {
284             mAdmin = admin;
285             return this;
286         }
287 
288         /**
289          * Sets whether the user is initialized.
290          */
291         @NonNull
setInitialized(boolean initialized)292         public UserInfoBuilder setInitialized(boolean initialized) {
293             mInitialized = initialized;
294             return this;
295         }
296 
297         /**
298          * Creates a new {@link UserInfo}.
299          */
300         @NonNull
build()301         public UserInfo build() {
302             int flags = mFlags;
303             if (mEphemeral) {
304                 flags |= UserInfo.FLAG_EPHEMERAL;
305             }
306             if (mAdmin) {
307                 flags |= UserInfo.FLAG_ADMIN;
308             }
309             if (mInitialized) {
310                 flags |= UserInfo.FLAG_INITIALIZED;
311             }
312             if (mGuest) {
313                 mType = UserManager.USER_TYPE_FULL_GUEST;
314             }
315             UserInfo info = new UserInfo(mUserId, mName, /* iconPath= */ null, flags, mType);
316             return info;
317         }
318     }
319 
UserTestingHelper()320     private UserTestingHelper() {
321         throw new UnsupportedOperationException("contains only static methods");
322     }
323 }
324