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.apitest;
18 
19 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
20 
21 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
22 
23 import static com.google.common.truth.Truth.assertWithMessage;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.annotation.UserIdInt;
28 import android.app.ActivityManager;
29 import android.car.Car;
30 import android.car.test.util.AndroidHelper;
31 import android.car.testapi.BlockingUserLifecycleListener;
32 import android.car.user.CarUserManager;
33 import android.car.user.UserCreationResult;
34 import android.car.user.UserRemovalResult;
35 import android.car.user.UserSwitchResult;
36 import android.car.util.concurrent.AsyncFuture;
37 import android.content.BroadcastReceiver;
38 import android.content.Context;
39 import android.content.Intent;
40 import android.content.IntentFilter;
41 import android.content.pm.UserInfo;
42 import android.os.Bundle;
43 import android.os.SystemProperties;
44 import android.os.UserHandle;
45 import android.os.UserManager;
46 import android.util.Log;
47 
48 import org.junit.After;
49 import org.junit.Before;
50 
51 import java.util.ArrayList;
52 import java.util.List;
53 import java.util.concurrent.CountDownLatch;
54 import java.util.concurrent.TimeUnit;
55 
56 /**
57  * Base class for tests that deal with multi user operations (creation, switch, etc)
58  *
59  */
60 abstract class CarMultiUserTestBase extends CarApiTestBase {
61 
62     private static final String TAG = CarMultiUserTestBase.class.getSimpleName();
63 
64     private static final long REMOVE_USER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30_000);
65     private static final long SWITCH_USER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30_000);
66 
67     private static final String PROP_STOP_BG_USERS_ON_SWITCH  = "fw.stop_bg_users_on_switch";
68 
69     private static final String NEW_USER_NAME_PREFIX = "CarApiTest.";
70 
71     protected CarUserManager mCarUserManager;
72     protected UserManager mUserManager;
73 
74     /**
75      * Current user before the test runs.
76      */
77     private UserInfo mInitialUser;
78 
79     private final CountDownLatch mUserRemoveLatch = new CountDownLatch(1);
80     private final List<Integer> mUsersToRemove = new ArrayList<>();
81 
82     // Guard to avoid test failure on @After when @Before failed (as it would hide the real issue)
83     private boolean mSetupFinished;
84 
85     @Before
setMultiUserFixtures()86     public final void setMultiUserFixtures() throws Exception {
87         Log.d(TAG, "setMultiUserFixtures() for " + mTestName.getMethodName());
88 
89         // Make sure user doesn't stop on switch (otherwise test process would crash)
90         setSystemProperty(PROP_STOP_BG_USERS_ON_SWITCH, "0");
91 
92         mCarUserManager = getCarService(Car.CAR_USER_SERVICE);
93         mUserManager = getContext().getSystemService(UserManager.class);
94 
95         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
96         getContext().registerReceiver(new BroadcastReceiver() {
97             @Override
98             public void onReceive(Context context, Intent intent) {
99                 mUserRemoveLatch.countDown();
100             }
101         }, filter);
102 
103         List<UserInfo> users = mUserManager.getAliveUsers();
104 
105         // Set current user
106         int currentUserId = getCurrentUserId();
107         Log.d(TAG, "Multi-user state on " + getTestName() + ": currentUser=" + currentUserId
108                 + ", aliveUsers=" + users);
109         for (UserInfo user : users) {
110             if (user.id == currentUserId) {
111                 mInitialUser = user;
112                 break;
113             }
114         }
115         assertWithMessage("user for currentId %s").that(mInitialUser).isNotNull();
116 
117         // Make sure current user is not a left-over from previous test
118         if (isUserCreatedByTheseTests(mInitialUser)) {
119             UserInfo properUser = null;
120             for (UserInfo user : users) {
121                 if (user.id != UserHandle.USER_SYSTEM && !isUserCreatedByTheseTests(user)) {
122                     properUser = user;
123                     break;
124                 }
125             }
126             assertWithMessage("found a proper user to switch from %s", mInitialUser.toFullString())
127                     .that(properUser).isNotNull();
128 
129             Log.i(TAG, "Current user on start of " + getTestName() + " is a dangling user: "
130                     + mInitialUser.toFullString() + "; switching to " + properUser.toFullString());
131             switchUser(properUser.id);
132             mInitialUser = properUser;
133         }
134 
135         // Remove dangling users from previous tests
136         for (UserInfo user : users) {
137             if (!isUserCreatedByTheseTests(user)) continue;
138             Log.e(TAG, "Removing dangling user " + user.toFullString() + " on @Before method of "
139                     + getTestName());
140             boolean removed = mUserManager.removeUser(user.id);
141             if (!removed) {
142                 Log.e(TAG, "user " + user.toFullString() + " was not removed");
143             }
144         }
145         mSetupFinished = true;
146     }
147 
resetStopUserOnSwitch()148     public final void resetStopUserOnSwitch() throws Exception {
149         setSystemProperty(PROP_STOP_BG_USERS_ON_SWITCH, "-1");
150     }
151 
152     @After
cleanupUserState()153     public final void cleanupUserState() throws Exception {
154         try {
155             if (!mSetupFinished) return;
156 
157             int currentUserId = getCurrentUserId();
158             int initialUserId = mInitialUser.id;
159             if (currentUserId != initialUserId) {
160                 Log.i(TAG, "Wrong current userId at the end of " + getTestName() + ": "
161                         + currentUserId + "; switching back to " + initialUserId);
162                 switchUser(initialUserId);
163 
164             }
165             if (!mUsersToRemove.isEmpty()) {
166                 Log.i(TAG, "removing users at end of  " + getTestName() + ": " + mUsersToRemove);
167                 for (Integer userId : mUsersToRemove) {
168                     if (hasUser(userId)) {
169                         removeUser(userId);
170                     }
171                 }
172             } else {
173                 Log.i(TAG, "no user to remove at end of " + getTestName());
174             }
175         } catch (Exception e) {
176             // Must catch otherwise it would be the test failure, which could hide the real issue
177             Log.e(TAG, "Caught exception on " + getTestName()
178                     + " disconnectCarAndCleanupUserState()", e);
179         }
180         finally {
181             resetStopUserOnSwitch();
182         }
183     }
184 
185     @UserIdInt
getCurrentUserId()186     protected int getCurrentUserId() {
187         return ActivityManager.getCurrentUser();
188     }
189 
190     @NonNull
createUser()191     protected UserInfo createUser() throws Exception {
192         return createUser("NonGuest");
193     }
194 
195     @NonNull
createUser(String name)196     protected UserInfo createUser(String name) throws Exception {
197         return createUser(name, /* isGuest= */ false);
198     }
199 
200     @NonNull
createGuest()201     protected UserInfo createGuest() throws Exception {
202         return createGuest("Guest");
203     }
204 
205     @NonNull
createGuest(String name)206     protected UserInfo createGuest(String name) throws Exception {
207         return createUser(name, /* isGuest= */ true);
208     }
209 
210     @NonNull
createUser(@ullable String name, boolean isGuest)211     private UserInfo createUser(@Nullable String name, boolean isGuest) throws Exception {
212         name = getNewUserName(name);
213         Log.d(TAG, "Creating new " + (isGuest ? "guest" : "user") + " with name '" + name
214                 + "' using CarUserManager");
215 
216         assertCanAddUser();
217 
218         UserCreationResult result = (isGuest
219                 ? mCarUserManager.createGuest(name)
220                 : mCarUserManager.createUser(name, /* flags= */ 0))
221                     .get(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
222         Log.d(TAG, "result: " + result);
223         assertWithMessage("user creation result (waited for %sms)", DEFAULT_WAIT_TIMEOUT_MS)
224                 .that(result).isNotNull();
225         assertWithMessage("user creation result (%s) success for user named %s", result, name)
226                 .that(result.isSuccess()).isTrue();
227         UserInfo user = result.getUser();
228         assertWithMessage("user on result %s", result).that(user).isNotNull();
229         mUsersToRemove.add(user.id);
230         assertWithMessage("new user %s is guest", user.toFullString()).that(user.isGuest())
231                 .isEqualTo(isGuest);
232         return result.getUser();
233     }
234 
getTestName()235     protected String getTestName() {
236         return getClass().getSimpleName() + "." + mTestName.getMethodName();
237     }
238 
getNewUserName(String name)239     private String getNewUserName(String name) {
240         StringBuilder newName = new StringBuilder(NEW_USER_NAME_PREFIX).append(getTestName());
241         if (name != null) {
242             newName.append('.').append(name);
243         }
244         return newName.toString();
245     }
246 
assertCanAddUser()247     protected void assertCanAddUser() {
248         Bundle restrictions = mUserManager.getUserRestrictions();
249         Log.d(TAG, "Restrictions for user " + getContext().getUserId() + ": "
250                 + AndroidHelper.toString(restrictions));
251         assertWithMessage("%s restriction", UserManager.DISALLOW_ADD_USER)
252                 .that(restrictions.getBoolean(UserManager.DISALLOW_ADD_USER, false)).isFalse();
253     }
254 
assertInitialUserIsAdmin()255     protected void assertInitialUserIsAdmin() {
256         assertWithMessage("initial user (%s) is admin", mInitialUser.toFullString())
257                 .that(mInitialUser.isAdmin()).isTrue();
258     }
259 
waitForUserRemoval(@serIdInt int userId)260     protected void waitForUserRemoval(@UserIdInt int userId) throws Exception {
261         boolean result = mUserRemoveLatch.await(REMOVE_USER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
262         assertWithMessage("User %s removed in %sms", userId, REMOVE_USER_TIMEOUT_MS)
263                 .that(result)
264                 .isTrue();
265     }
266 
switchUser(@serIdInt int userId)267     protected void switchUser(@UserIdInt int userId) throws Exception {
268         boolean waitForUserSwitchToComplete = true;
269         // If current user is the target user, no life cycle event is expected.
270         if (getCurrentUserId() == userId) waitForUserSwitchToComplete = false;
271 
272         Log.d(TAG, "registering listener for user switching");
273         BlockingUserLifecycleListener listener = BlockingUserLifecycleListener
274                 .forSpecificEvents()
275                 .forUser(userId)
276                 .setTimeout(SWITCH_USER_TIMEOUT_MS)
277                 .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
278                 .build();
279         mCarUserManager.addListener(Runnable::run, listener);
280 
281         try {
282             Log.i(TAG, "Switching to user " + userId + " using CarUserManager");
283             AsyncFuture<UserSwitchResult> future = mCarUserManager.switchUser(userId);
284             UserSwitchResult result = future.get(SWITCH_USER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
285             Log.d(TAG, "Result: " + result);
286 
287             assertWithMessage("User %s switched in %sms. Result: %s", userId,
288                     SWITCH_USER_TIMEOUT_MS, result).that(result.isSuccess()).isTrue();
289 
290             if (waitForUserSwitchToComplete) {
291                 listener.waitForEvents();
292             }
293         } finally {
294             mCarUserManager.removeListener(listener);
295         }
296 
297         Log.d(TAG, "User switch complete. User id: " + userId);
298     }
299 
removeUser(@serIdInt int userId)300     protected void removeUser(@UserIdInt int userId) {
301         Log.d(TAG, "Removing user " + userId);
302 
303         UserRemovalResult result = mCarUserManager.removeUser(userId);
304         Log.d(TAG, "result: " + result);
305         assertWithMessage("User %s removed. Result: %s", userId, result)
306                 .that(result.isSuccess()).isTrue();
307     }
308 
309     @Nullable
getUser(@serIdInt int id)310     protected UserInfo getUser(@UserIdInt int id) {
311         List<UserInfo> list = mUserManager.getUsers();
312 
313         for (UserInfo user : list) {
314             if (user.id == id) {
315                 return user;
316             }
317         }
318         return null;
319     }
320 
hasUser(@serIdInt int id)321     protected boolean hasUser(@UserIdInt int id) {
322         return getUser(id) != null;
323     }
324 
setSystemProperty(String property, String value)325     protected void setSystemProperty(String property, String value) {
326         String oldValue = SystemProperties.get(property);
327         Log.d(TAG, "Setting system prop " + property + " from '" + oldValue + "' to '"
328                 + value + "'");
329 
330         // NOTE: must use Shell command as SystemProperties.set() requires SELinux permission check
331         // (so invokeWithShellPermissions() would not be enough)
332         runShellCommand("setprop %s %s", property, value);
333         Log.v(TAG, "Set: " + SystemProperties.get(property));
334     }
335 
336 
assertUserInfo(UserInfo actualUser, UserInfo expectedUser)337     protected void assertUserInfo(UserInfo actualUser, UserInfo expectedUser) {
338         assertWithMessage("Wrong id for user %s", actualUser.toFullString())
339                 .that(actualUser.id).isEqualTo(expectedUser.id);
340         assertWithMessage("Wrong name for user %s", actualUser.toFullString())
341                 .that(actualUser.name).isEqualTo(expectedUser.name);
342         assertWithMessage("Wrong type for user %s", actualUser.toFullString())
343                 .that(actualUser.userType).isEqualTo(expectedUser.userType);
344         assertWithMessage("Wrong flags for user %s", actualUser.toFullString())
345                 .that(actualUser.flags).isEqualTo(expectedUser.flags);
346     }
347 
isUserCreatedByTheseTests(UserInfo user)348     private static boolean isUserCreatedByTheseTests(UserInfo user) {
349         return user.name != null && user.name.startsWith(NEW_USER_NAME_PREFIX);
350     }
351 }
352