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