• 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 android.car.hiddenapitest;
18 
19 import static android.car.test.util.UserTestingHelper.setMaxSupportedUsers;
20 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
21 
22 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
23 
24 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
25 
26 import static com.google.common.truth.Truth.assertWithMessage;
27 
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.annotation.UserIdInt;
31 import android.app.ActivityManager;
32 import android.car.Car;
33 import android.car.CarOccupantZoneManager;
34 import android.car.SyncResultCallback;
35 import android.car.extendedapitest.testbase.CarApiTestBase;
36 import android.car.test.util.AndroidHelper;
37 import android.car.test.util.UserTestingHelper;
38 import android.car.testapi.BlockingUserLifecycleListener;
39 import android.car.user.CarUserManager;
40 import android.car.user.UserCreationRequest;
41 import android.car.user.UserCreationResult;
42 import android.car.user.UserRemovalRequest;
43 import android.car.user.UserStartRequest;
44 import android.car.user.UserStopRequest;
45 import android.car.user.UserSwitchRequest;
46 import android.content.BroadcastReceiver;
47 import android.content.Context;
48 import android.content.Intent;
49 import android.content.IntentFilter;
50 import android.content.pm.UserInfo;
51 import android.os.Bundle;
52 import android.os.SystemProperties;
53 import android.os.UserHandle;
54 import android.os.UserManager;
55 import android.util.Log;
56 
57 import org.junit.After;
58 import org.junit.AssumptionViolatedException;
59 import org.junit.Before;
60 
61 import java.util.ArrayList;
62 import java.util.List;
63 import java.util.concurrent.CountDownLatch;
64 import java.util.concurrent.TimeUnit;
65 
66 /**
67  * Base class for tests that deal with multi user operations (creation, switch, etc)
68  *
69  */
70 abstract class CarMultiUserTestBase extends CarApiTestBase {
71 
72     private static final String TAG = CarMultiUserTestBase.class.getSimpleName();
73 
74     private static final long REMOVE_USER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30_000);
75     private static final long SWITCH_USER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30_000);
76 
77     private static final String NEW_USER_NAME_PREFIX = "CarApiTest.";
78 
79     private static final int sMaxNumberUsersBefore = UserManager.getMaxSupportedUsers();
80     private static boolean sChangedMaxNumberUsers;
81 
82     protected CarOccupantZoneManager mCarOccupantZoneManager;
83     protected CarUserManager mCarUserManager;
84     protected UserManager mUserManager;
85 
86     /**
87      * Current user before the test runs.
88      */
89     private UserInfo mInitialUser;
90 
91     private final CountDownLatch mUserRemoveLatch = new CountDownLatch(1);
92     private final List<Integer> mUsersToRemove = new ArrayList<>();
93 
94     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
95         @Override
96         public void onReceive(Context context, Intent intent) {
97             Log.d(TAG, "Received a broadcast: " + AndroidHelper.toString(intent));
98             mUserRemoveLatch.countDown();
99         }
100     };
101 
102     // Guard to avoid test failure on @After when @Before failed (as it would hide the real issue)
103     private boolean mSetupFinished;
104 
105     @Before
setMultiUserFixtures()106     public final void setMultiUserFixtures() throws Exception {
107         Log.d(TAG, "setMultiUserFixtures() for " + getTestName());
108 
109         mCarOccupantZoneManager = getCarService(Car.CAR_OCCUPANT_ZONE_SERVICE);
110         mCarUserManager = getCarService(Car.CAR_USER_SERVICE);
111         mUserManager = getContext().getSystemService(UserManager.class);
112 
113         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
114         getContext().registerReceiver(mReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
115         Log.d(TAG, "Registered a broadcast receiver: " + mReceiver
116                 + " with filter: " + filter);
117 
118         List<UserInfo> users = mUserManager.getAliveUsers();
119 
120         // Set current user
121         int currentUserId = getCurrentUserId();
122         Log.d(TAG, "Multi-user state on " + getTestName() + ": currentUser=" + currentUserId
123                 + ", aliveUsers=" + users);
124         for (UserInfo user : users) {
125             if (user.id == currentUserId) {
126                 mInitialUser = user;
127                 break;
128             }
129         }
130         assertWithMessage("user for currentId %s", currentUserId).that(mInitialUser).isNotNull();
131 
132         // Make sure current user is not a left-over from previous test
133         if (isUserCreatedByTheseTests(mInitialUser)) {
134             UserInfo properUser = null;
135             for (UserInfo user : users) {
136                 if (user.id != UserHandle.USER_SYSTEM && !isUserCreatedByTheseTests(user)) {
137                     properUser = user;
138                     break;
139                 }
140             }
141             assertWithMessage("found a proper user to switch from %s", mInitialUser.toFullString())
142                     .that(properUser).isNotNull();
143 
144             Log.i(TAG, "Current user on start of " + getTestName() + " is a dangling user: "
145                     + mInitialUser.toFullString() + "; switching to " + properUser.toFullString());
146             switchUser(properUser.id);
147             mInitialUser = properUser;
148         }
149 
150         // Remove dangling users from previous tests
151         for (UserInfo user : users) {
152             if (!isUserCreatedByTheseTests(user)) continue;
153             Log.e(TAG, "Removing dangling user " + user.toFullString() + " on @Before method of "
154                     + getTestName());
155             boolean removed = mUserManager.removeUser(user.id);
156             if (!removed) {
157                 Log.e(TAG, "user " + user.toFullString() + " was not removed");
158             }
159         }
160 
161         Log.d(TAG, "setMultiUserFixtures(): Saul Goodman, setting mSetupFinished to true");
162         mSetupFinished = true;
163     }
164 
165     @After
cleanupUserState()166     public final void cleanupUserState() throws Exception {
167         try {
168             if (!mSetupFinished) {
169                 Log.w(TAG, "skipping cleanupUserState() because mSetupFinished is false");
170                 return;
171             }
172 
173             getContext().unregisterReceiver(mReceiver);
174             Log.d(TAG, "Unregistered a broadcast receiver: " + mReceiver);
175 
176             int currentUserId = getCurrentUserId();
177             int initialUserId = mInitialUser.id;
178             if (currentUserId != initialUserId) {
179                 Log.i(TAG, "Wrong current userId at the end of " + getTestName() + ": "
180                         + currentUserId + "; switching back to " + initialUserId);
181                 switchUser(initialUserId);
182 
183             }
184             if (!mUsersToRemove.isEmpty()) {
185                 Log.i(TAG, "removing users at end of " + getTestName() + ": " + mUsersToRemove);
186                 for (Integer userId : mUsersToRemove) {
187                     if (hasUser(userId)) {
188                         mUserManager.removeUser(userId);
189                     }
190                 }
191             } else {
192                 Log.i(TAG, "no user to remove at end of " + getTestName());
193             }
194         } catch (Exception e) {
195             // Must catch otherwise it would be the test failure, which could hide the real issue
196             Log.e(TAG, "Caught exception on " + getTestName()
197                     + " disconnectCarAndCleanupUserState()", e);
198         }
199     }
200 
setupMaxNumberOfUsers(int requiredUsers)201     protected static void setupMaxNumberOfUsers(int requiredUsers) {
202         if (sMaxNumberUsersBefore < requiredUsers) {
203             sChangedMaxNumberUsers = true;
204             Log.i(TAG, "Increasing maximizing number of users from " + sMaxNumberUsersBefore
205                     + " to " + requiredUsers);
206             setMaxSupportedUsers(requiredUsers);
207         }
208     }
209 
restoreMaxNumberOfUsers()210     protected static void restoreMaxNumberOfUsers() {
211         if (sChangedMaxNumberUsers) {
212             Log.i(TAG, "Restoring maximum number of users to " + sMaxNumberUsersBefore);
213             setMaxSupportedUsers(sMaxNumberUsersBefore);
214         }
215     }
216 
217     @UserIdInt
getCurrentUserId()218     protected int getCurrentUserId() {
219         return ActivityManager.getCurrentUser();
220     }
221 
222     @NonNull
createUser()223     protected UserInfo createUser() throws Exception {
224         return createUser("NonGuest");
225     }
226 
227     @NonNull
createUser(String name)228     protected UserInfo createUser(String name) throws Exception {
229         return createUser(name, /* isGuest= */ false);
230     }
231 
232     @NonNull
createGuest()233     protected UserInfo createGuest() throws Exception {
234         return createGuest("Guest");
235     }
236 
237     @NonNull
createGuest(String name)238     protected UserInfo createGuest(String name) throws Exception {
239         return createUser(name, /* isGuest= */ true);
240     }
241 
242     @NonNull
createUser(@ullable String name, boolean isGuest)243     private UserInfo createUser(@Nullable String name, boolean isGuest) throws Exception {
244         name = getNewUserName(name);
245         Log.d(TAG, "Creating new " + (isGuest ? "guest" : "user") + " with name '" + name
246                 + "' using CarUserManager");
247 
248         assertCanAddUser();
249 
250         UserCreationRequest.Builder userCreationRequestBuilder = new UserCreationRequest.Builder();
251 
252         if (isGuest) {
253             userCreationRequestBuilder.setGuest();
254         }
255 
256         SyncResultCallback<UserCreationResult> userCreationResultCallback =
257                 new SyncResultCallback<>();
258 
259         mCarUserManager.createUser(userCreationRequestBuilder.setName(name).build(), Runnable::run,
260                 userCreationResultCallback);
261 
262         UserCreationResult result = userCreationResultCallback.get(
263                 DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
264 
265         Log.d(TAG, "result: " + result);
266         assertWithMessage("user creation result (waited for %sms)", DEFAULT_WAIT_TIMEOUT_MS)
267                 .that(result).isNotNull();
268         assertWithMessage("user creation result (%s) success for user named %s", result, name)
269                 .that(result.isSuccess()).isTrue();
270         UserHandle user = result.getUser();
271         assertWithMessage("user on result %s", result).that(user).isNotNull();
272         mUsersToRemove.add(user.getIdentifier());
273         assertWithMessage("new user %s is guest", user.toString())
274                 .that(mUserManager.isGuestUser(user.getIdentifier()))
275                 .isEqualTo(isGuest);
276         return mUserManager.getUserInfo(result.getUser().getIdentifier());
277     }
278 
getNewUserName(String name)279     private String getNewUserName(String name) {
280         StringBuilder newName = new StringBuilder(NEW_USER_NAME_PREFIX).append(getTestName());
281         if (name != null) {
282             newName.append('.').append(name);
283         }
284         return newName.toString();
285     }
286 
assertCanAddUser()287     protected void assertCanAddUser() {
288         Bundle restrictions = mUserManager.getUserRestrictions();
289         Log.d(TAG, "Restrictions for user " + getContext().getUser() + ": "
290                 + AndroidHelper.toString(restrictions));
291         assertWithMessage("%s restriction", UserManager.DISALLOW_ADD_USER)
292                 .that(restrictions.getBoolean(UserManager.DISALLOW_ADD_USER, false)).isFalse();
293     }
294 
assertInitialUserIsAdmin()295     protected void assertInitialUserIsAdmin() {
296         assertWithMessage("initial user (%s) is admin", mInitialUser.toFullString())
297                 .that(mInitialUser.isAdmin()).isTrue();
298     }
299 
waitForUserRemoval(@serIdInt int userId)300     protected void waitForUserRemoval(@UserIdInt int userId) throws Exception {
301         boolean result = mUserRemoveLatch.await(REMOVE_USER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
302         assertWithMessage("User %s removed in %sms", userId, REMOVE_USER_TIMEOUT_MS)
303                 .that(result)
304                 .isTrue();
305     }
306 
switchUser(@serIdInt int userId)307     protected void switchUser(@UserIdInt int userId) throws Exception {
308         boolean waitForUserSwitchToComplete = true;
309         // If current user is the target user, no life cycle event is expected.
310         if (getCurrentUserId() == userId) waitForUserSwitchToComplete = false;
311 
312         Log.d(TAG, "registering listener for user switching");
313         BlockingUserLifecycleListener listener = BlockingUserLifecycleListener
314                 .forSpecificEvents()
315                 .forUser(userId)
316                 .setTimeout(SWITCH_USER_TIMEOUT_MS)
317                 .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
318                 .build();
319         mCarUserManager.addListener(Runnable::run, listener);
320 
321         try {
322             Log.i(TAG, "Switching to user " + userId + " using CarUserManager");
323             mCarUserManager.switchUser(new UserSwitchRequest.Builder(
324                     UserHandle.of(userId)).build(), Runnable::run, response -> {
325                     Log.d(TAG, "result: " + response);
326                     assertWithMessage("User %s switched in %sms. Result: %s", userId,
327                         SWITCH_USER_TIMEOUT_MS, response).that(response.isSuccess()).isTrue();
328                 }
329             );
330 
331             if (waitForUserSwitchToComplete) {
332                 listener.waitForEvents();
333             }
334         } finally {
335             mCarUserManager.removeListener(listener);
336         }
337 
338         Log.d(TAG, "User switch complete. User id: " + userId);
339     }
340 
removeUser(@serIdInt int userId)341     protected void removeUser(@UserIdInt int userId) throws Exception {
342         Log.d(TAG, "Removing user " + userId);
343 
344         mCarUserManager.removeUser(new UserRemovalRequest.Builder(
345                 UserHandle.of(userId)).build(), Runnable::run, response -> {
346                     Log.d(TAG, "result: " + response);
347                     assertWithMessage("User %s removed. Result: %s", userId, response)
348                             .that(response.isSuccess()).isTrue();
349                 }
350         );
351     }
352 
startUserInBackgroundOnSecondaryDisplay(@serIdInt int userId, int displayId)353     protected void startUserInBackgroundOnSecondaryDisplay(@UserIdInt int userId, int displayId)
354             throws Exception {
355         Log.i(TAG, "Starting background user " + userId + " on display " + displayId);
356 
357         UserStartRequest request = new UserStartRequest.Builder(UserHandle.of(userId))
358                 .setDisplayId(displayId).build();
359         mCarUserManager.startUser(request, Runnable::run,
360                 response ->
361                     assertWithMessage("startUser success for user %s on display %s",
362                             userId, displayId).that(response.isSuccess()).isTrue());
363     }
364 
forceStopUser(@serIdInt int userId)365     protected void forceStopUser(@UserIdInt int userId) throws Exception {
366         Log.i(TAG, "Force-stopping user " + userId);
367 
368         UserStopRequest request =
369                 new UserStopRequest.Builder(UserHandle.of(userId)).setForce().build();
370         mCarUserManager.stopUser(request, Runnable::run,
371                 response ->
372                     assertWithMessage("stopUser success for user %s", userId)
373                             .that(response.isSuccess()).isTrue());
374     }
375 
376     @Nullable
getUser(@serIdInt int id)377     protected UserInfo getUser(@UserIdInt int id) {
378         List<UserInfo> list = mUserManager.getUsers();
379 
380         for (UserInfo user : list) {
381             if (user.id == id) {
382                 return user;
383             }
384         }
385         return null;
386     }
387 
hasUser(@serIdInt int id)388     protected boolean hasUser(@UserIdInt int id) {
389         return getUser(id) != null;
390     }
391 
setSystemProperty(String property, String value)392     protected void setSystemProperty(String property, String value) {
393         String oldValue = SystemProperties.get(property);
394         Log.d(TAG, "Setting system prop " + property + " from '" + oldValue + "' to '"
395                 + value + "'");
396 
397         // NOTE: must use Shell command as SystemProperties.set() requires SELinux permission check
398         // (so invokeWithShellPermissions() would not be enough)
399         runShellCommand("setprop %s %s", property, value);
400         Log.v(TAG, "Set: " + SystemProperties.get(property));
401     }
402 
assertUserInfo(UserInfo actualUser, UserInfo expectedUser)403     protected void assertUserInfo(UserInfo actualUser, UserInfo expectedUser) {
404         assertWithMessage("Wrong id for user %s", actualUser.toFullString())
405                 .that(actualUser.id).isEqualTo(expectedUser.id);
406         assertWithMessage("Wrong name for user %s", actualUser.toFullString())
407                 .that(actualUser.name).isEqualTo(expectedUser.name);
408         assertWithMessage("Wrong type for user %s", actualUser.toFullString())
409                 .that(actualUser.userType).isEqualTo(expectedUser.userType);
410         assertWithMessage("Wrong flags for user %s", actualUser.toFullString())
411                 .that(actualUser.flags).isEqualTo(expectedUser.flags);
412     }
413 
isUserCreatedByTheseTests(UserInfo user)414     private static boolean isUserCreatedByTheseTests(UserInfo user) {
415         return user.name != null && user.name.startsWith(NEW_USER_NAME_PREFIX);
416     }
417 
418     /**
419      * Checks if the target device supports MUMD (multi-user multi-display).
420      * @throws AssumptionViolatedException if the device does not support MUMD.
421      */
requireMumd()422     protected static void requireMumd() {
423         UserTestingHelper.requireMumd(getTargetContext());
424     }
425 
426     /**
427      * Returns a secondary display that is available to start a background user on.
428      *
429      * @return the id of a secondary display that is not assigned to any user, if any.
430      * @throws IllegalStateException when there is no secondary display available.
431      */
getDisplayForStartingBackgroundUser()432     protected int getDisplayForStartingBackgroundUser() {
433         return UserTestingHelper.getDisplayForStartingBackgroundUser(
434                 getTargetContext(), mCarOccupantZoneManager);
435     }
436 
getTargetContext()437     private static Context getTargetContext() {
438         return getInstrumentation().getTargetContext();
439     }
440 }
441