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