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 import static android.os.UserHandle.USER_SYSTEM; 20 21 import static com.google.common.truth.Truth.assertThat; 22 import static com.google.common.truth.Truth.assertWithMessage; 23 24 import android.app.ActivityManager; 25 import android.app.IStopUserCallback; 26 import android.content.Context; 27 import android.content.pm.UserInfo; 28 import android.os.RemoteException; 29 import android.os.UserManager; 30 import android.platform.test.annotations.Postsubmit; 31 import android.provider.Settings; 32 import android.util.Log; 33 34 import androidx.test.InstrumentationRegistry; 35 import androidx.test.filters.LargeTest; 36 import androidx.test.runner.AndroidJUnit4; 37 38 import com.android.internal.util.FunctionalUtils; 39 40 import org.junit.After; 41 import org.junit.Before; 42 import org.junit.Test; 43 import org.junit.runner.RunWith; 44 45 import java.io.IOException; 46 import java.util.List; 47 import java.util.concurrent.CountDownLatch; 48 import java.util.concurrent.TimeUnit; 49 50 /** 51 * To run the test: 52 * atest FrameworksServicesTests:com.android.server.pm.UserLifecycleStressTest 53 */ 54 @Postsubmit 55 @RunWith(AndroidJUnit4.class) 56 @LargeTest 57 public class UserLifecycleStressTest { 58 private static final String TAG = "UserLifecycleStressTest"; 59 // TODO: Make this smaller once we have improved it. 60 private static final int TIMEOUT_IN_SECOND = 40; 61 private static final int NUM_ITERATIONS = 8; 62 private static final int WAIT_BEFORE_STOP_USER_IN_SECOND = 3; 63 64 private Context mContext; 65 private UserManager mUserManager; 66 private ActivityManager mActivityManager; 67 private UserSwitchWaiter mUserSwitchWaiter; 68 private String mRemoveGuestOnExitOriginalValue; 69 70 @Before setup()71 public void setup() throws RemoteException { 72 mContext = InstrumentationRegistry.getInstrumentation().getContext(); 73 mUserManager = mContext.getSystemService(UserManager.class); 74 mActivityManager = mContext.getSystemService(ActivityManager.class); 75 mUserSwitchWaiter = new UserSwitchWaiter(TAG, TIMEOUT_IN_SECOND); 76 mRemoveGuestOnExitOriginalValue = Settings.Global.getString(mContext.getContentResolver(), 77 Settings.Global.REMOVE_GUEST_ON_EXIT); 78 } 79 80 @After tearDown()81 public void tearDown() throws IOException { 82 mUserSwitchWaiter.close(); 83 Settings.Global.putString(mContext.getContentResolver(), 84 Settings.Global.REMOVE_GUEST_ON_EXIT, mRemoveGuestOnExitOriginalValue); 85 } 86 87 /** 88 * Create and stop user {@link #NUM_ITERATIONS} times in a row. Check stop user can be finished 89 * in a reasonable amount of time. 90 */ 91 @Test stopManagedProfileStressTest()92 public void stopManagedProfileStressTest() throws RemoteException, InterruptedException { 93 for (int i = 0; i < NUM_ITERATIONS; i++) { 94 final UserInfo userInfo = mUserManager.createProfileForUser("TestUser", 95 UserManager.USER_TYPE_PROFILE_MANAGED, 0, mActivityManager.getCurrentUser()); 96 assertThat(userInfo).isNotNull(); 97 try { 98 assertWithMessage("Failed to start the profile") 99 .that(ActivityManager.getService().startUserInBackground(userInfo.id)) 100 .isTrue(); 101 // Seems the broadcast queue is getting more busy if we wait a few seconds before 102 // stopping the user. 103 TimeUnit.SECONDS.sleep(WAIT_BEFORE_STOP_USER_IN_SECOND); 104 stopUser(userInfo.id); 105 } finally { 106 mUserManager.removeUser(userInfo.id); 107 } 108 } 109 } 110 111 /** 112 * Starts over the guest user {@link #NUM_ITERATIONS} times in a row. 113 * 114 * Starting over the guest means the following: 115 * 1. While the guest user is in foreground, mark it for deletion. 116 * 2. Create a new guest. (This wouldn't be possible if the old one wasn't marked for deletion) 117 * 3. Switch to newly created guest. 118 * 4. Remove the previous guest after the switch is complete. 119 **/ 120 @Test switchToExistingGuestAndStartOverStressTest()121 public void switchToExistingGuestAndStartOverStressTest() { 122 Settings.Global.putString(mContext.getContentResolver(), 123 Settings.Global.REMOVE_GUEST_ON_EXIT, "0"); 124 125 if (ActivityManager.getCurrentUser() != USER_SYSTEM) { 126 switchUser(USER_SYSTEM); 127 } 128 129 final List<UserInfo> guestUsers = mUserManager.getGuestUsers(); 130 int nextGuestId = guestUsers.isEmpty() ? USER_NULL : guestUsers.get(0).id; 131 132 for (int i = 0; i < NUM_ITERATIONS; i++) { 133 final int currentGuestId = nextGuestId; 134 135 Log.d(TAG, "switchToExistingGuestAndStartOverStressTest" 136 + " - Run " + (i + 1) + " / " + NUM_ITERATIONS); 137 138 if (currentGuestId != USER_NULL) { 139 Log.d(TAG, "Switching to the existing guest"); 140 switchUser(currentGuestId); 141 142 Log.d(TAG, "Marking current guest for deletion"); 143 assertWithMessage("Couldn't mark guest for deletion") 144 .that(mUserManager.markGuestForDeletion(currentGuestId)) 145 .isTrue(); 146 } 147 148 Log.d(TAG, "Creating a new guest"); 149 final UserInfo newGuest = mUserManager.createGuest(mContext); 150 assertWithMessage("Couldn't create new guest") 151 .that(newGuest) 152 .isNotNull(); 153 154 Log.d(TAG, "Switching to the new guest"); 155 switchUser(newGuest.id); 156 157 if (currentGuestId != USER_NULL) { 158 Log.d(TAG, "Removing the previous guest"); 159 assertWithMessage("Couldn't remove guest") 160 .that(mUserManager.removeUser(currentGuestId)) 161 .isTrue(); 162 } 163 164 Log.d(TAG, "Switching back to the system user"); 165 switchUser(USER_SYSTEM); 166 167 nextGuestId = newGuest.id; 168 } 169 if (nextGuestId != USER_NULL) { 170 Log.d(TAG, "Removing the last created guest user"); 171 mUserManager.removeUser(nextGuestId); 172 } 173 Log.d(TAG, "testSwitchToExistingGuestAndStartOver - End"); 174 } 175 176 /** Stops the given user and waits for the stop to finish. */ stopUser(int userId)177 private void stopUser(int userId) throws RemoteException, InterruptedException { 178 runWithLatch("stop user", countDownLatch -> { 179 ActivityManager.getService() 180 .stopUser(userId, /* force= */ true, new IStopUserCallback.Stub() { 181 @Override 182 public void userStopped(int userId) { 183 countDownLatch.countDown(); 184 } 185 186 @Override 187 public void userStopAborted(int i) throws RemoteException { 188 189 } 190 }); 191 }); 192 } 193 194 /** Starts the given user in the foreground and waits for the switch to finish. */ switchUser(int userId)195 private void switchUser(int userId) { 196 Log.d(TAG, "Switching to user " + userId); 197 198 mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(userId, () -> { 199 assertWithMessage("Could not start switching to user " + userId) 200 .that(mActivityManager.switchUser(userId)).isTrue(); 201 }, /* onFail= */ () -> { 202 throw new AssertionError("Could not complete switching to user " + userId); 203 }); 204 } 205 206 /** 207 * Calls the given consumer with a CountDownLatch parameter, and expects it's countDown() method 208 * to be called before timeout, or fails the test otherwise. 209 */ runWithLatch(String tag, FunctionalUtils.RemoteExceptionIgnoringConsumer<CountDownLatch> consumer)210 private void runWithLatch(String tag, 211 FunctionalUtils.RemoteExceptionIgnoringConsumer<CountDownLatch> consumer) 212 throws RemoteException, InterruptedException { 213 final CountDownLatch countDownLatch = new CountDownLatch(1); 214 final long startTime = System.currentTimeMillis(); 215 216 consumer.acceptOrThrow(countDownLatch); 217 final boolean doneBeforeTimeout = countDownLatch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS); 218 assertWithMessage("Took more than " + TIMEOUT_IN_SECOND + "s to " + tag) 219 .that(doneBeforeTimeout) 220 .isTrue(); 221 222 final long elapsedTime = System.currentTimeMillis() - startTime; 223 Log.d(TAG, tag + " takes " + elapsedTime + " ms"); 224 } 225 } 226 227