• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.platform.helpers;
17 
18 import android.annotation.Nullable;
19 import android.app.ActivityManager;
20 import android.car.Car;
21 import android.car.CarOccupantZoneManager;
22 import android.car.SyncResultCallback;
23 import android.car.user.CarUserManager;
24 import android.car.user.CarUserManager.UserLifecycleListener;
25 import android.car.user.UserCreationRequest;
26 import android.car.user.UserCreationResult;
27 import android.car.user.UserStartResponse;
28 import android.car.user.UserStartRequest;
29 import android.car.user.UserSwitchResult;
30 import android.car.user.UserStopRequest;
31 import android.car.user.UserStopResponse;
32 import android.car.util.concurrent.AsyncFuture;
33 import android.content.Context;
34 import android.content.pm.UserInfo;
35 import android.os.Build;
36 import android.os.SystemClock;
37 import android.os.UserManager;
38 import android.support.test.uiautomator.UiDevice;
39 import android.util.Log;
40 
41 import androidx.test.InstrumentationRegistry;
42 
43 import com.android.compatibility.common.util.SystemUtil;
44 
45 import java.io.IOException;
46 import java.util.concurrent.CountDownLatch;
47 import java.util.concurrent.TimeUnit;
48 
49 /**
50  * Helper class that is used by integration test only. It is wrapping around exiting platform APIs
51  * {@link CarUserManager}, {@link UserManager} to expose functions for user switch end-to-end tests.
52  */
53 public class MultiUserHelper {
54     private static final int CREATE_USER_TIMEOUT_MS = 20_000;
55     private static final String LOG_TAG = MultiUserHelper.class.getSimpleName();
56 
57     /** For testing purpose we allow a wide range of switching time. */
58     private static final int USER_SWITCH_TIMEOUT_SECOND = 300;
59 
60     private static final String CREATE_USER_COMMAND = "cmd car_service create-user ";
61 
62     private static MultiUserHelper sMultiUserHelper;
63     private CarUserManager mCarUserManager;
64     private UserManager mUserManager;
65     private ActivityManager mActivityManager;
66     private CarOccupantZoneManager mCarOccupantZoneManager;
67 
MultiUserHelper()68     private MultiUserHelper() {
69         Context context = InstrumentationRegistry.getTargetContext();
70         mUserManager = UserManager.get(context);
71         Car car = Car.createCar(context);
72         mCarUserManager = (CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE);
73         mActivityManager = context.getSystemService(ActivityManager.class);
74         mCarOccupantZoneManager = car.getCarManager(CarOccupantZoneManager.class);
75     }
76 
77     /**
78      * It will always be used as a singleton class
79      *
80      * @return MultiUserHelper instance
81      */
getInstance()82     public static MultiUserHelper getInstance() {
83         if (sMultiUserHelper == null) {
84             sMultiUserHelper = new MultiUserHelper();
85         }
86         return sMultiUserHelper;
87     }
88 
89     /**
90      * Creates a user if it does not exist already.
91      *
92      * @param name the name of the user or guest
93      * @param isGuestUser true if want to create a guest, otherwise create a regular user
94      * @return User Id for newly created user or existing user if it already exists
95      */
createUserIfDoesNotExist(String name, boolean isGuestUser)96     public int createUserIfDoesNotExist(String name, boolean isGuestUser) throws Exception {
97         UserInfo userInfo = getUserByName(name);
98         if (userInfo != null) {
99             return userInfo.id;
100         }
101         return createUser(name, isGuestUser);
102     }
103 
104     /**
105      * Creates a regular user or guest
106      *
107      * @param name the name of the user or guest
108      * @param isGuestUser true if want to create a guest, otherwise create a regular user
109      * @return User Id for newly created user
110      */
createUser(String name, boolean isGuestUser)111     public int createUser(String name, boolean isGuestUser) throws Exception {
112         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
113             Log.d(
114                     LOG_TAG,
115                     "An Android version earlier than UDC is detected. Using the shell for user "
116                             + "creation");
117             return createUserUsingShell(name, isGuestUser);
118         }
119         Log.d(
120                 LOG_TAG,
121                 "Creating new "
122                         + (isGuestUser ? "guest" : "user")
123                         + " with name '"
124                         + name
125                         + "' using CarUserManager");
126         SyncResultCallback<UserCreationResult> userCreationResultCallback =
127                 new SyncResultCallback<>();
128         if (isGuestUser) {
129             // Create a new guest
130             mCarUserManager.createUser(
131                     new UserCreationRequest.Builder().setName(name).setGuest().build(),
132                     Runnable::run,
133                     userCreationResultCallback);
134         } else {
135             // Create a new user
136             mCarUserManager.createUser(
137                     new UserCreationRequest.Builder().setName(name).build(),
138                     Runnable::run,
139                     userCreationResultCallback);
140         }
141         UserCreationResult result =
142                 userCreationResultCallback.get(CREATE_USER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
143         if (!result.isSuccess()) {
144             throw new Exception(String.format("The user was not created successfully: %s", result));
145         }
146         return result.getUser().getIdentifier();
147     }
148 
createUserUsingShell(String name, boolean isGuest)149     private int createUserUsingShell(String name, boolean isGuest) throws Exception {
150         String retStr;
151         Log.d(
152                 LOG_TAG,
153                 "Creating new " + (isGuest ? "guest" : "user") + " with name '" + name + "'");
154         if (isGuest) {
155             retStr = SystemUtil.runShellCommand(CREATE_USER_COMMAND + "--guest " + name);
156         } else {
157             retStr = SystemUtil.runShellCommand(CREATE_USER_COMMAND + name);
158         }
159         if (!retStr.contains("STATUS_SUCCESSFUL")) {
160             throw new Exception(
161                     "failed to create a new user: "
162                             + name
163                             + ". User creation shell command output: "
164                             + retStr);
165         }
166         // Extract the user ID out and return it
167         String userIdPattern = "id=";
168         int newUserId = -1;
169         if (retStr.contains(userIdPattern) && retStr.split(userIdPattern).length > 1) {
170             newUserId = Integer.parseInt(retStr.split(userIdPattern)[1].trim());
171         }
172         return newUserId;
173     }
174 
175     /**
176      * Switches to the target user at API level. Always waits until user switch complete. Besides,
177      * it waits for an additional amount of time for switched user to become idle (stable)
178      *
179      * @param id user id
180      * @param timeoutMs the time to wait (in msec) after user switch complete
181      */
switchAndWaitForStable(int id, long timeoutMs)182     public void switchAndWaitForStable(int id, long timeoutMs) throws Exception {
183         switchToUserId(id);
184         SystemClock.sleep(timeoutMs);
185     }
186 
187     /**
188      * Switches to the target user at API level. Always wait until user switch complete.
189      *
190      * <p>User switch complete only means the user ready at API level. It doesn't mean the UI is
191      * completely ready for the target user. It doesn't include unlocking user data and loading car
192      * launcher page
193      *
194      * @param id Id of the user to switch to
195      */
switchToUserId(int id)196     public void switchToUserId(int id) throws Exception {
197         Log.d(
198                 LOG_TAG,
199                 String.format(
200                         "Switching from user %d to user %d",
201                         getCurrentForegroundUserInfo().id, id));
202         final CountDownLatch latch = new CountDownLatch(1);
203         // A UserLifeCycleListener to wait for user switch event. It is equivalent to
204         // UserSwitchObserver#onUserSwitchComplete callback
205         // TODO(b/155434907): Should eventually wait for "user unlocked" event which is better
206         UserLifecycleListener userSwitchListener =
207                 e -> {
208                     if (e.getEventType() == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
209                         latch.countDown();
210                     }
211                 };
212         mCarUserManager.addListener(Runnable::run, userSwitchListener);
213         AsyncFuture<UserSwitchResult> future = mCarUserManager.switchUser(id);
214         UserSwitchResult result = null;
215         try {
216             result = future.get(USER_SWITCH_TIMEOUT_SECOND, TimeUnit.SECONDS);
217         } catch (Exception e) {
218             throw new Exception(
219                     String.format("Exception when switching to target user: %d", id), e);
220         }
221 
222         if (!result.isSuccess()) {
223             throw new Exception(String.format("User switch failed: %s", result));
224         }
225         // Wait for user switch complete event, which seems to happen later than UserSwitchResult.
226         if (!latch.await(USER_SWITCH_TIMEOUT_SECOND, TimeUnit.SECONDS)) {
227             throw new Exception(
228                     String.format(
229                             "Timeout while switching to user %d after %d seconds",
230                             id, USER_SWITCH_TIMEOUT_SECOND));
231         }
232         mCarUserManager.removeListener(userSwitchListener);
233     }
234 
235     /**
236      * Starts a user on a display. Awaits {@link CarUserManager#USER_LIFECYCLE_EVENT_TYPE_STARTING} event.
237      *
238      * <p>User start complete only means the user ready at API level. It doesn't mean the UI is
239      * completely ready for the target user. It doesn't include unlocking user data and loading car
240      * launcher page
241      *
242      * @param id Id of the user to start
243      * @param displayId Id of the display to start the user on
244      */
startUser(int id, int displayId)245     public void startUser(int id, int displayId) throws Exception {
246         final CountDownLatch latch = new CountDownLatch(1);
247         UserLifecycleListener userStartListener =
248                 e -> {
249                     if (e.getEventType() == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING) {
250                         latch.countDown();
251                     }
252                 };
253         mCarUserManager.addListener(Runnable::run, userStartListener);
254         UserStartRequest userStartRequest =
255                 new UserStartRequest.Builder(mUserManager.getUserInfo(id).getUserHandle())
256                         .setDisplayId(displayId)
257                         .build();
258         Log.d(LOG_TAG, String.format("Starting a user with id %d on display %d", id, displayId));
259         SyncResultCallback<UserStartResponse> userStartResponseCallback =
260                 new SyncResultCallback<>();
261         mCarUserManager.startUser(userStartRequest, Runnable::run, userStartResponseCallback);
262         UserStartResponse response =
263                 userStartResponseCallback.get(USER_SWITCH_TIMEOUT_SECOND, TimeUnit.SECONDS);
264         if (!response.isSuccess()) {
265             throw new Exception(
266                     String.format(
267                             "Failed to start user %d on display %d: %s", id, displayId, response));
268         }
269         if (!latch.await(USER_SWITCH_TIMEOUT_SECOND, TimeUnit.SECONDS)) {
270             throw new Exception(
271                     String.format(
272                             "Timeout while starting user %d after %d seconds",
273                             id, USER_SWITCH_TIMEOUT_SECOND));
274         }
275         mCarUserManager.removeListener(userStartListener);
276     }
277 
278     /**
279      * Stops a user.
280      *
281      * @param id Id of the user to stop
282      */
stopUser(int id)283     public void stopUser(int id) throws Exception {
284         UserStopRequest userStopRequest =
285                 new UserStopRequest.Builder(mUserManager.getUserInfo(id).getUserHandle()).build();
286         SyncResultCallback<UserStopResponse> userStopResponseCallback = new SyncResultCallback<>();
287         mCarUserManager.stopUser(userStopRequest, Runnable::run, userStopResponseCallback);
288         UserStopResponse response =
289                 userStopResponseCallback.get(USER_SWITCH_TIMEOUT_SECOND, TimeUnit.SECONDS);
290         if (!response.isSuccess()) {
291             throw new Exception(String.format("Failed to stop user %d: %s", id, response));
292         }
293     }
294 
295     /**
296      * Removes the target user. For now it is a non-blocking call.
297      *
298      * @param userInfo info of the user to be removed
299      * @return true if removed successfully
300      */
removeUser(UserInfo userInfo)301     public boolean removeUser(UserInfo userInfo) {
302         return removeUser(userInfo.id);
303     }
304 
305     /**
306      * Removes the target user. For now it is a non-blocking call.
307      *
308      * @param userId id of the user to be removed
309      * @return true if removed successfully
310      */
removeUser(int userId)311     public boolean removeUser(int userId) {
312         return mUserManager.removeUser(userId);
313     }
314 
getCurrentForegroundUserInfo()315     public UserInfo getCurrentForegroundUserInfo() {
316         return mUserManager.getUserInfo(ActivityManager.getCurrentUser());
317     }
318 
319     /**
320      * Get default initial user
321      *
322      * @return user ID of initial user
323      */
getInitialUser()324     public int getInitialUser() {
325         UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
326         try {
327             String userId = device.executeShellCommand("cmd car_service get-initial-user").trim();
328             return Integer.parseInt(userId);
329         } catch (IOException e) {
330             throw new RuntimeException("Failed to get initial user", e);
331         }
332     }
333 
334     /**
335      * Tries to find an existing user with the given name
336      *
337      * @param name the name of the user
338      * @return A {@link UserInfo} if the user is found, or {@code null} if not found
339      */
340     @Nullable
getUserByName(String name)341     public UserInfo getUserByName(String name) {
342         return mUserManager.getUsers().stream()
343                 .filter(user -> user.name.equals(name))
344                 .findFirst()
345                 .orElse(null);
346     }
347 
348     /**
349      * Returns the list of displays that are available for starting visible background users.
350      *
351      * @return The list of displays that are available for starting visible background users.
352      */
getDisplayIdsForStartingVisibleBackgroundUsers()353     public int[] getDisplayIdsForStartingVisibleBackgroundUsers() {
354         return mActivityManager.getDisplayIdsForStartingVisibleBackgroundUsers();
355     }
356 
357     /**
358      * Returns the user id for the given display id.
359      *
360      * @param displayId The display id.
361      * @return The user id for the given display id.
362      */
getUserForDisplayId(int displayId)363     public int getUserForDisplayId(int displayId) {
364         return mCarOccupantZoneManager.getUserForDisplayId(displayId);
365     }
366 }
367