• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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