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