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