1 /*
2  * Copyright (C) 2019 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.app.activity;
18 
19 import static android.app.ActivityThread.shouldReportChange;
20 import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE;
21 import static android.app.servertransaction.ActivityLifecycleItem.ON_DESTROY;
22 import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
23 import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME;
24 import static android.app.servertransaction.ActivityLifecycleItem.ON_START;
25 import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
26 import static android.content.pm.ActivityInfo.CONFIG_FONT_SCALE;
27 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
28 
29 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
30 
31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
32 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
33 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
34 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
35 
36 import static org.junit.Assert.assertEquals;
37 import static org.junit.Assert.assertFalse;
38 import static org.junit.Assert.assertTrue;
39 import static org.mockito.ArgumentMatchers.any;
40 import static org.mockito.ArgumentMatchers.anyInt;
41 import static org.mockito.Mockito.clearInvocations;
42 import static org.mockito.Mockito.doNothing;
43 import static org.mockito.Mockito.never;
44 import static org.mockito.Mockito.timeout;
45 import static org.mockito.Mockito.verify;
46 
47 import android.app.Activity;
48 import android.app.ActivityClient;
49 import android.app.ActivityThread;
50 import android.app.ActivityThread.ActivityClientRecord;
51 import android.app.LoadedApk;
52 import android.app.servertransaction.PendingTransactionActions;
53 import android.content.ComponentName;
54 import android.content.Context;
55 import android.content.Intent;
56 import android.content.pm.ActivityInfo;
57 import android.content.pm.ApplicationInfo;
58 import android.content.res.Configuration;
59 import android.os.IBinder;
60 import android.os.UserHandle;
61 import android.platform.test.annotations.Presubmit;
62 import android.testing.PollingCheck;
63 import android.view.WindowManagerGlobal;
64 import android.window.SizeConfigurationBuckets;
65 
66 import androidx.test.annotation.UiThreadTest;
67 import androidx.test.ext.junit.runners.AndroidJUnit4;
68 import androidx.test.filters.MediumTest;
69 import androidx.test.platform.app.InstrumentationRegistry;
70 
71 import org.junit.Test;
72 import org.junit.runner.RunWith;
73 import org.mockito.Mockito;
74 import org.mockito.MockitoSession;
75 import org.mockito.quality.Strictness;
76 
77 import java.util.concurrent.TimeUnit;
78 
79 /**
80  * Test for verifying {@link android.app.ActivityThread} class.
81  *
82  * <p>Build/Install/Run:
83  *  atest FrameworksMockingCoreTests:android.app.activity.ActivityThreadClientTest
84  *
85  * <p>This test class is a part of Window Manager Service tests and specified in
86  * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
87  */
88 @RunWith(AndroidJUnit4.class)
89 @MediumTest
90 @Presubmit
91 public class ActivityThreadClientTest {
92     private static final long WAIT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
93 
94     @Test
95     @UiThreadTest
testLifecycleAfterFinished_OnCreate()96     public void testLifecycleAfterFinished_OnCreate() throws Exception {
97         try (ClientMockSession clientSession = new ClientMockSession()) {
98             ActivityClientRecord r = clientSession.stubActivityRecord();
99 
100             Activity activity = clientSession.launchActivity(r);
101             activity.finish();
102             assertEquals(ON_CREATE, r.getLifecycleState());
103 
104             clientSession.startActivity(r);
105             assertEquals(ON_CREATE, r.getLifecycleState());
106 
107             clientSession.resumeActivity(r);
108             assertEquals(ON_CREATE, r.getLifecycleState());
109 
110             clientSession.pauseActivity(r);
111             assertEquals(ON_CREATE, r.getLifecycleState());
112 
113             clientSession.stopActivity(r);
114             assertEquals(ON_CREATE, r.getLifecycleState());
115 
116             clientSession.destroyActivity(r);
117             assertEquals(ON_DESTROY, r.getLifecycleState());
118         }
119     }
120 
121     @Test
122     @UiThreadTest
testLifecycleAfterFinished_OnStart()123     public void testLifecycleAfterFinished_OnStart() throws Exception {
124         try (ClientMockSession clientSession = new ClientMockSession()) {
125             ActivityClientRecord r = clientSession.stubActivityRecord();
126 
127             Activity activity = clientSession.launchActivity(r);
128             clientSession.startActivity(r);
129             activity.finish();
130             assertEquals(ON_START, r.getLifecycleState());
131 
132             clientSession.resumeActivity(r);
133             assertEquals(ON_START, r.getLifecycleState());
134 
135             clientSession.pauseActivity(r);
136             assertEquals(ON_START, r.getLifecycleState());
137 
138             clientSession.stopActivity(r);
139             assertEquals(ON_STOP, r.getLifecycleState());
140 
141             clientSession.destroyActivity(r);
142             assertEquals(ON_DESTROY, r.getLifecycleState());
143         }
144     }
145 
146     @Test
147     @UiThreadTest
testLifecycleAfterFinished_OnResume()148     public void testLifecycleAfterFinished_OnResume() throws Exception {
149         try (ClientMockSession clientSession = new ClientMockSession()) {
150             ActivityClientRecord r = clientSession.stubActivityRecord();
151 
152             Activity activity = clientSession.launchActivity(r);
153             clientSession.startActivity(r);
154             clientSession.resumeActivity(r);
155             activity.finish();
156             assertEquals(ON_RESUME, r.getLifecycleState());
157 
158             clientSession.pauseActivity(r);
159             assertEquals(ON_PAUSE, r.getLifecycleState());
160 
161             clientSession.stopActivity(r);
162             assertEquals(ON_STOP, r.getLifecycleState());
163 
164             clientSession.destroyActivity(r);
165             assertEquals(ON_DESTROY, r.getLifecycleState());
166         }
167     }
168 
169     @Test
testLifecycleOfRelaunch()170     public void testLifecycleOfRelaunch() throws Exception {
171         try (ClientMockSession clientSession = new ClientMockSession()) {
172             ActivityThread activityThread = clientSession.mockThread();
173             ActivityClientRecord r = clientSession.stubActivityRecord();
174             final TestActivity[] activity = new TestActivity[1];
175 
176             // Verify for ON_CREATE state. Activity should not be relaunched.
177             getInstrumentation().runOnMainSync(() -> {
178                 activity[0] = (TestActivity) clientSession.launchActivity(r);
179             });
180             recreateAndVerifyNoRelaunch(activityThread, activity[0]);
181 
182             // Verify for ON_START state. Activity should be relaunched.
183             getInstrumentation().runOnMainSync(() -> clientSession.startActivity(r));
184             recreateAndVerifyRelaunched(activityThread, activity[0], r, ON_PAUSE);
185 
186             // Verify for ON_RESUME state. Activity should be relaunched.
187             getInstrumentation().runOnMainSync(() -> clientSession.resumeActivity(r));
188             recreateAndVerifyRelaunched(activityThread, activity[0], r, ON_RESUME);
189 
190             // Verify for ON_PAUSE state. Activity should be relaunched.
191             getInstrumentation().runOnMainSync(() -> clientSession.pauseActivity(r));
192             recreateAndVerifyRelaunched(activityThread, activity[0], r, ON_PAUSE);
193 
194             // Verify for ON_STOP state. Activity should be relaunched.
195             getInstrumentation().runOnMainSync(() -> clientSession.stopActivity(r));
196             recreateAndVerifyRelaunched(activityThread, activity[0], r, ON_STOP);
197 
198             // Verify for ON_DESTROY state. Activity should not be relaunched.
199             getInstrumentation().runOnMainSync(() -> clientSession.destroyActivity(r));
200             recreateAndVerifyNoRelaunch(activityThread, activity[0]);
201         }
202     }
203 
204     @Test
testShouldReportChange()205     public void testShouldReportChange() {
206         final Configuration newConfig = new Configuration();
207         final Configuration currentConfig = new Configuration();
208 
209         assertFalse("Must not report change if no public diff",
210                 shouldReportChange(currentConfig, newConfig, null /* sizeBuckets */,
211                         0 /* handledConfigChanges */, false /* alwaysReportChange */));
212 
213         final int[] verticalThresholds = {100, 400};
214         final SizeConfigurationBuckets buckets = new SizeConfigurationBuckets(
215                 null /* horizontal */,
216                 verticalThresholds,
217                 null /* smallest */,
218                 null /* screenLayoutSize */,
219                 false /* screenLayoutLongSet */);
220         currentConfig.screenHeightDp = 200;
221         newConfig.screenHeightDp = 300;
222 
223         assertFalse("Must not report changes if the diff is small and not handled",
224                 shouldReportChange(currentConfig, newConfig, buckets,
225                         CONFIG_FONT_SCALE /* handledConfigChanges */,
226                         false /* alwaysReportChange */));
227 
228         assertTrue("Must report changes if the small diff is handled",
229                 shouldReportChange(currentConfig, newConfig, buckets,
230                         CONFIG_SCREEN_SIZE /* handledConfigChanges */,
231                         false /* alwaysReportChange */));
232 
233         assertTrue("Must report changes if it should, even it is small and not handled",
234                 shouldReportChange(currentConfig, newConfig, buckets,
235                         CONFIG_FONT_SCALE /* handledConfigChanges */,
236                         true /* alwaysReportChange */));
237 
238         currentConfig.fontScale = 0.8f;
239         newConfig.fontScale = 1.2f;
240 
241         assertTrue("Must report handled changes regardless of small unhandled change",
242                 shouldReportChange(currentConfig, newConfig, buckets,
243                         CONFIG_FONT_SCALE /* handledConfigChanges */,
244                         false /* alwaysReportChange */));
245 
246         newConfig.screenHeightDp = 500;
247 
248         assertFalse("Must not report changes if there's unhandled big changes",
249                 shouldReportChange(currentConfig, newConfig, buckets,
250                         CONFIG_FONT_SCALE /* handledConfigChanges */,
251                         false /* alwaysReportChange */));
252     }
253 
recreateAndVerifyNoRelaunch(ActivityThread activityThread, TestActivity activity)254     private void recreateAndVerifyNoRelaunch(ActivityThread activityThread, TestActivity activity) {
255         clearInvocations(activityThread);
256         getInstrumentation().runOnMainSync(() -> activity.recreate());
257         getInstrumentation().waitForIdleSync();
258 
259         verify(activityThread, never()).handleRelaunchActivity(any(), any());
260     }
261 
recreateAndVerifyRelaunched(ActivityThread activityThread, TestActivity activity, ActivityClientRecord r, int expectedState)262     private void recreateAndVerifyRelaunched(ActivityThread activityThread, TestActivity activity,
263             ActivityClientRecord r, int expectedState) throws Exception {
264         clearInvocations(activityThread);
265         getInstrumentation().runOnMainSync(() -> activity.recreate());
266 
267         verify(activityThread, timeout(WAIT_TIMEOUT_MS)).handleRelaunchActivity(any(), any());
268 
269         // Wait for the relaunch to complete.
270         PollingCheck.check("Waiting for the expected state " + expectedState + " timeout",
271                 WAIT_TIMEOUT_MS,
272                 () -> expectedState == r.getLifecycleState());
273         assertEquals(expectedState, r.getLifecycleState());
274     }
275 
276     private class ClientMockSession implements AutoCloseable {
277         private MockitoSession mMockSession;
278         private ActivityThread mThread;
279 
ClientMockSession()280         private ClientMockSession() {
281             mThread = ActivityThread.currentActivityThread();
282             mMockSession = mockitoSession()
283                     .strictness(Strictness.LENIENT)
284                     .spyStatic(ActivityClient.class)
285                     .spyStatic(WindowManagerGlobal.class)
286                     .startMocking();
287             doReturn(Mockito.mock(WindowManagerGlobal.class))
288                     .when(WindowManagerGlobal::getInstance);
289             final ActivityClient mockAc = Mockito.mock(ActivityClient.class);
290             doReturn(mockAc).when(ActivityClient::getInstance);
291             doReturn(true).when(mockAc).finishActivity(any() /* token */,
292                     anyInt() /* resultCode */, any() /* resultData */, anyInt() /* finishTask */);
293         }
294 
launchActivity(ActivityClientRecord r)295         private Activity launchActivity(ActivityClientRecord r) {
296             return mThread.handleLaunchActivity(r, null /* pendingActions */,
297                     Context.DEVICE_ID_DEFAULT, null /* customIntent */);
298         }
299 
startActivity(ActivityClientRecord r)300         private void startActivity(ActivityClientRecord r) {
301             mThread.handleStartActivity(r, null /* pendingActions */, null /* activityOptions */);
302         }
303 
resumeActivity(ActivityClientRecord r)304         private void resumeActivity(ActivityClientRecord r) {
305             mThread.handleResumeActivity(r, true /* finalStateRequest */,
306                     true /* isForward */, false /* shouldSendCompatFakeFocus */, "test");
307         }
308 
pauseActivity(ActivityClientRecord r)309         private void pauseActivity(ActivityClientRecord r) {
310             mThread.handlePauseActivity(r, false /* finished */,
311                     false /* userLeaving */, 0 /* configChanges */, false /* autoEnteringPip */,
312                     null /* pendingActions */, "test");
313         }
314 
stopActivity(ActivityClientRecord r)315         private void stopActivity(ActivityClientRecord r) {
316             mThread.handleStopActivity(r, 0 /* configChanges */,
317                     new PendingTransactionActions(), false /* finalStateRequest */, "test");
318         }
319 
destroyActivity(ActivityClientRecord r)320         private void destroyActivity(ActivityClientRecord r) {
321             mThread.handleDestroyActivity(r, true /* finishing */, 0 /* configChanges */,
322                     false /* getNonConfigInstance */, "test");
323         }
324 
mockThread()325         private ActivityThread mockThread() {
326             spyOn(mThread);
327             return mThread;
328         }
329 
stubActivityRecord()330         private ActivityClientRecord stubActivityRecord() {
331             ComponentName component = new ComponentName(
332                     InstrumentationRegistry.getInstrumentation().getContext(), TestActivity.class);
333             ActivityInfo info = new ActivityInfo();
334             info.packageName = component.getPackageName();
335             info.name = component.getClassName();
336             info.exported = true;
337             info.applicationInfo = new ApplicationInfo();
338             info.applicationInfo.packageName = info.packageName;
339             info.applicationInfo.uid = UserHandle.myUserId();
340             info.applicationInfo.sourceDir = "/test/sourceDir";
341 
342             // mock the function for preventing NPE in emulator environment. The purpose of the
343             // test is for activity client state changes, we don't focus on the updating for the
344             // ApplicationInfo.
345             LoadedApk packageInfo = mThread.peekPackageInfo(info.packageName,
346                     true /* includeCode */);
347             spyOn(packageInfo);
348             doNothing().when(packageInfo).updateApplicationInfo(any(), any());
349 
350             return new ActivityClientRecord(mock(IBinder.class), Intent.makeMainActivity(component),
351                     0 /* ident */, info, new Configuration(), null /* referrer */,
352                     null /* voiceInteractor */, null /* state */, null /* persistentState */,
353                     null /* pendingResults */, null /* pendingNewIntents */,
354                     null /* activityOptions */, true /* isForward */, null /* profilerInfo */,
355                     mThread /* client */, null /* asssitToken */, null /* shareableActivityToken */,
356                     false /* launchedFromBubble */, null /* taskfragmentToken */);
357         }
358 
359         @Override
close()360         public void close() {
361             mMockSession.finishMocking();
362         }
363     }
364 
365     // Test activity
366     public static class TestActivity extends Activity {
367     }
368 }
369