1 /* 2 * Copyright (C) 2017 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 com.android.server.pm; 17 18 import static android.os.UserHandle.USER_NULL; 19 20 import static com.google.common.truth.Truth.assertThat; 21 import static com.google.common.truth.Truth.assertWithMessage; 22 23 import static org.junit.Assume.assumeFalse; 24 25 import android.app.ActivityManager; 26 import android.app.IStopUserCallback; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.pm.UserInfo; 30 import android.os.RemoteException; 31 import android.os.UserHandle; 32 import android.os.UserManager; 33 import android.platform.test.annotations.Postsubmit; 34 import android.provider.Settings; 35 import android.util.Log; 36 37 import androidx.test.InstrumentationRegistry; 38 import androidx.test.filters.LargeTest; 39 import androidx.test.runner.AndroidJUnit4; 40 41 import com.android.compatibility.common.util.BlockingBroadcastReceiver; 42 import com.android.compatibility.common.util.ShellUtils; 43 import com.android.internal.util.FunctionalUtils; 44 45 import org.junit.After; 46 import org.junit.Before; 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 50 import java.io.IOException; 51 import java.util.List; 52 import java.util.concurrent.CountDownLatch; 53 import java.util.concurrent.TimeUnit; 54 import java.util.concurrent.TimeoutException; 55 56 /** 57 * To run the test: 58 * atest FrameworksServicesTests:com.android.server.pm.UserLifecycleStressTest 59 */ 60 @Postsubmit 61 @RunWith(AndroidJUnit4.class) 62 @LargeTest 63 public class UserLifecycleStressTest { 64 private static final String TAG = "UserLifecycleStressTest"; 65 // TODO: Make this smaller once we have improved it. 66 private static final int TIMEOUT_IN_SECOND = 40; 67 private static final int CHECK_USER_REMOVED_INTERVAL_MS = 500; 68 69 private static final int NUM_ITERATIONS = 8; 70 private static final int WAIT_BEFORE_STOP_USER_IN_SECOND = 3; 71 72 /** Name of users/profiles in the test. Users with this name may be freely removed. */ 73 private static final String TEST_USER_NAME = "UserLifecycleStressTest_test_user"; 74 75 private Context mContext; 76 private UserManager mUserManager; 77 private ActivityManager mActivityManager; 78 private UserSwitchWaiter mUserSwitchWaiter; 79 private String mRemoveGuestOnExitOriginalValue; 80 private int mOriginalCurrentUserId; 81 82 @Before setup()83 public void setup() throws RemoteException { 84 mContext = InstrumentationRegistry.getInstrumentation().getContext(); 85 mUserManager = mContext.getSystemService(UserManager.class); 86 mActivityManager = mContext.getSystemService(ActivityManager.class); 87 mUserSwitchWaiter = new UserSwitchWaiter(TAG, TIMEOUT_IN_SECOND); 88 mRemoveGuestOnExitOriginalValue = Settings.Global.getString(mContext.getContentResolver(), 89 Settings.Global.REMOVE_GUEST_ON_EXIT); 90 waitForBroadcastBarrier(); // isolate tests from each other 91 mOriginalCurrentUserId = ActivityManager.getCurrentUser(); 92 } 93 94 @After tearDown()95 public void tearDown() throws IOException { 96 switchUser(mOriginalCurrentUserId); 97 mUserSwitchWaiter.close(); 98 Settings.Global.putString(mContext.getContentResolver(), 99 Settings.Global.REMOVE_GUEST_ON_EXIT, mRemoveGuestOnExitOriginalValue); 100 waitForBroadcastBarrier(); // isolate tests from each other 101 } 102 103 /** 104 * Create and stop user {@link #NUM_ITERATIONS} times in a row. Check stop user can be finished 105 * in a reasonable amount of time. 106 */ 107 @Test stopManagedProfileStressTest()108 public void stopManagedProfileStressTest() throws RemoteException, InterruptedException { 109 UserHandle mainUser = mUserManager.getMainUser(); 110 assumeFalse("There is no main user", mainUser == null); 111 switchUser(mainUser.getIdentifier()); 112 113 for (int i = 0; i < NUM_ITERATIONS; i++) { 114 logIteration(i, "stopManagedProfileStressTest"); 115 116 final UserInfo userInfo = mUserManager.createProfileForUser(TEST_USER_NAME, 117 UserManager.USER_TYPE_PROFILE_MANAGED, 0, ActivityManager.getCurrentUser()); 118 assertThat(userInfo).isNotNull(); 119 try { 120 assertWithMessage("Failed to start the profile") 121 .that(ActivityManager.getService().startUserInBackground(userInfo.id)) 122 .isTrue(); 123 // Seems the broadcast queue is getting more busy if we wait a few seconds before 124 // stopping the user. 125 TimeUnit.SECONDS.sleep(WAIT_BEFORE_STOP_USER_IN_SECOND); 126 stopUser(userInfo.id); 127 } finally { 128 mUserManager.removeUser(userInfo.id); 129 } 130 } 131 } 132 133 /** 134 * Create a user, and then remove it immediately after starting it in background 135 * {@link #NUM_ITERATIONS} times in a row. 136 * Check device is not crashed when user data directory is deleted while some other processes 137 * might still be trying to access those deleted files. 138 */ 139 @Test removeRecentlyStartedUserStressTest()140 public void removeRecentlyStartedUserStressTest() throws RemoteException, InterruptedException { 141 for (int i = 0; i < NUM_ITERATIONS; i++) { 142 logIteration(i, "removeRecentlyStartedUserStressTest"); 143 144 Log.d(TAG, "Creating a new user"); 145 final UserInfo userInfo = mUserManager.createUser(TEST_USER_NAME, 146 UserManager.USER_TYPE_FULL_SECONDARY, 0); 147 assertWithMessage("Failed to create the user") 148 .that(userInfo) 149 .isNotNull(); 150 try { 151 Log.d(TAG, "Starting user " + userInfo.id); 152 startUserInBackgroundAndWaitForUserStartedBroadcast(userInfo.id); 153 } finally { 154 Log.d(TAG, "Removing user " + userInfo.id); 155 assertWithMessage("Failed to remove the user " + userInfo.id) 156 .that(removeUser(userInfo.id)) 157 .isTrue(); 158 } 159 } 160 } 161 162 /** 163 * Starts over the guest user {@link #NUM_ITERATIONS} times in a row. 164 * 165 * Starting over the guest means the following: 166 * 1. While the guest user is in foreground, mark it for deletion. 167 * 2. Create a new guest. (This wouldn't be possible if the old one wasn't marked for deletion) 168 * 3. Switch to newly created guest. 169 * 4. Remove the previous guest after the switch is complete. 170 **/ 171 @Test switchToExistingGuestAndStartOverStressTest()172 public void switchToExistingGuestAndStartOverStressTest() { 173 Settings.Global.putString(mContext.getContentResolver(), 174 Settings.Global.REMOVE_GUEST_ON_EXIT, "0"); 175 176 final List<UserInfo> guestUsers = mUserManager.getGuestUsers(); 177 int nextGuestId = guestUsers.isEmpty() ? USER_NULL : guestUsers.get(0).id; 178 179 for (int i = 0; i < NUM_ITERATIONS; i++) { 180 logIteration(i, "switchToExistingGuestAndStartOverStressTest"); 181 182 final int currentGuestId = nextGuestId; 183 184 if (currentGuestId != USER_NULL) { 185 Log.d(TAG, "Switching to the existing guest"); 186 switchUser(currentGuestId); 187 188 Log.d(TAG, "Marking current guest for deletion"); 189 assertWithMessage("Couldn't mark guest for deletion") 190 .that(mUserManager.markGuestForDeletion(currentGuestId)) 191 .isTrue(); 192 } 193 194 Log.d(TAG, "Creating a new guest"); 195 final UserInfo newGuest = mUserManager.createGuest(mContext); 196 assertWithMessage("Couldn't create new guest") 197 .that(newGuest) 198 .isNotNull(); 199 200 Log.d(TAG, "Switching to the new guest"); 201 switchUser(newGuest.id); 202 203 if (currentGuestId != USER_NULL) { 204 Log.d(TAG, "Removing the previous guest"); 205 assertWithMessage("Couldn't remove guest") 206 .that(mUserManager.removeUser(currentGuestId)) 207 .isTrue(); 208 } 209 210 Log.d(TAG, "Switching back to the initial user"); 211 switchUser(mOriginalCurrentUserId); 212 213 nextGuestId = newGuest.id; 214 } 215 if (nextGuestId != USER_NULL) { 216 Log.d(TAG, "Removing the last created guest user"); 217 mUserManager.removeUser(nextGuestId); 218 } 219 Log.d(TAG, "testSwitchToExistingGuestAndStartOver - End"); 220 } 221 removeUser(int userId)222 private boolean removeUser(int userId) { 223 if (!mUserManager.removeUser(userId)) { 224 return false; 225 } 226 try { 227 final long startTime = System.currentTimeMillis(); 228 final long timeoutInMs = TIMEOUT_IN_SECOND * 1000; 229 while (mUserManager.getUserInfo(userId) != null 230 && System.currentTimeMillis() - startTime < timeoutInMs) { 231 TimeUnit.MILLISECONDS.sleep(CHECK_USER_REMOVED_INTERVAL_MS); 232 } 233 } catch (InterruptedException e) { 234 Thread.currentThread().interrupt(); 235 } catch (Exception e) { 236 // Ignore 237 } 238 return mUserManager.getUserInfo(userId) == null; 239 } 240 241 /** Stops the given user and waits for the stop to finish. */ stopUser(int userId)242 private void stopUser(int userId) throws RemoteException, InterruptedException { 243 runWithLatch("stop user", countDownLatch -> { 244 ActivityManager.getService() 245 .stopUserWithCallback(userId, new IStopUserCallback.Stub() { 246 @Override 247 public void userStopped(int userId) { 248 countDownLatch.countDown(); 249 } 250 251 @Override 252 public void userStopAborted(int i) throws RemoteException { 253 254 } 255 }); 256 }); 257 } 258 259 /** Starts the given user in the foreground and waits for the switch to finish. */ switchUser(int userId)260 private void switchUser(int userId) { 261 if (ActivityManager.getCurrentUser() == userId) { 262 Log.d(TAG, "No need to switch, current user is already user " + userId); 263 return; 264 } 265 Log.d(TAG, "Switching to user " + userId); 266 267 mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(userId, () -> { 268 assertWithMessage("Could not start switching to user " + userId) 269 .that(mActivityManager.switchUser(userId)).isTrue(); 270 }, /* onFail= */ () -> { 271 throw new AssertionError("Could not complete switching to user " + userId); 272 }); 273 } 274 275 /** 276 * Start user in background and wait for {@link Intent#ACTION_USER_STARTED} broadcast. 277 * <p> To start in foreground instead, see {@link #switchUser(int)}. 278 * <p> This should always be used for profiles since profiles cannot be started in foreground. 279 */ startUserInBackgroundAndWaitForUserStartedBroadcast(int userId)280 private void startUserInBackgroundAndWaitForUserStartedBroadcast(int userId) { 281 runWithBlockingBroadcastReceiver("start user and wait for ACTION_USER_STARTED broadcast", 282 userId, Intent.ACTION_USER_STARTED, 283 () -> ActivityManager.getService().startUserInBackground(userId)); 284 } 285 286 /** 287 * Calls the given runnable and expects the given broadcast to be received before timeout, 288 * or fails the test otherwise. 289 * @param tag tag for logging 290 * @param userId id of the user to register the broadcast receiver with 291 * see {@link Context#registerReceiverAsUser} 292 * @param action action of the broadcast intent filter i.e. {@link Intent#ACTION_USER_STARTED} 293 * @param runnable this will be called after registering the broadcast receiver 294 */ runWithBlockingBroadcastReceiver(String tag, int userId, String action, FunctionalUtils.ThrowingRunnable runnable)295 private void runWithBlockingBroadcastReceiver(String tag, int userId, String action, 296 FunctionalUtils.ThrowingRunnable runnable) { 297 try (BlockingBroadcastReceiver blockingBroadcastReceiver = new BlockingBroadcastReceiver( 298 mContext, action, 299 intent -> intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL) == userId)) { 300 blockingBroadcastReceiver.setTimeout(TIMEOUT_IN_SECOND); 301 blockingBroadcastReceiver.registerForAllUsers(); 302 runnable.run(); 303 assertWithMessage("Took more than " + TIMEOUT_IN_SECOND + "s to " + tag) 304 .that(blockingBroadcastReceiver.awaitForBroadcast()) 305 .isNotNull(); 306 } 307 } 308 309 /** 310 * Calls the given consumer with a CountDownLatch parameter, and expects it's countDown() method 311 * to be called before timeout, or fails the test otherwise. 312 */ runWithLatch(String tag, FunctionalUtils.RemoteExceptionIgnoringConsumer<CountDownLatch> consumer)313 private void runWithLatch(String tag, 314 FunctionalUtils.RemoteExceptionIgnoringConsumer<CountDownLatch> consumer) 315 throws RemoteException, InterruptedException { 316 final CountDownLatch countDownLatch = new CountDownLatch(1); 317 final long startTime = System.currentTimeMillis(); 318 319 consumer.acceptOrThrow(countDownLatch); 320 final boolean doneBeforeTimeout = countDownLatch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS); 321 assertWithMessage("Took more than " + TIMEOUT_IN_SECOND + "s to " + tag) 322 .that(doneBeforeTimeout) 323 .isTrue(); 324 325 final long elapsedTime = System.currentTimeMillis() - startTime; 326 Log.d(TAG, tag + " takes " + elapsedTime + " ms"); 327 } 328 logIteration(int iteration, String testMethodName)329 private void logIteration(int iteration, String testMethodName) { 330 Log.d(TAG, testMethodName + " - Iteration " + (iteration + 1) + " / " + NUM_ITERATIONS); 331 } 332 waitForBroadcastBarrier()333 private static void waitForBroadcastBarrier() { 334 try { 335 Log.d(TAG, "Starting to waitForBroadcastBarrier"); 336 ShellUtils.runShellCommandWithTimeout("am wait-for-broadcast-barrier", 337 TIMEOUT_IN_SECOND); 338 Log.d(TAG, "waitForBroadcastBarrier is finished"); 339 } catch (TimeoutException e) { 340 Log.e(TAG, "Timeout while running waitForBroadcastBarrier", e); 341 } 342 } 343 } 344 345