1 /*
2  * Copyright 2018 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.content.Intent.ACTION_EDIT;
20 import static android.content.Intent.ACTION_VIEW;
21 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
22 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
23 import static android.view.Display.INVALID_DISPLAY;
24 
25 import static com.google.common.truth.Truth.assertThat;
26 
27 import static org.junit.Assert.assertEquals;
28 import static org.junit.Assert.assertFalse;
29 import static org.junit.Assert.assertNotEquals;
30 import static org.junit.Assert.assertTrue;
31 
32 import android.annotation.Nullable;
33 import android.app.Activity;
34 import android.app.ActivityManager;
35 import android.app.ActivityThread;
36 import android.app.ActivityThread.ActivityClientRecord;
37 import android.app.IApplicationThread;
38 import android.app.PictureInPictureParams;
39 import android.app.ResourcesManager;
40 import android.app.servertransaction.ActivityConfigurationChangeItem;
41 import android.app.servertransaction.ActivityRelaunchItem;
42 import android.app.servertransaction.ClientTransaction;
43 import android.app.servertransaction.ClientTransactionItem;
44 import android.app.servertransaction.ConfigurationChangeItem;
45 import android.app.servertransaction.NewIntentItem;
46 import android.app.servertransaction.ResumeActivityItem;
47 import android.app.servertransaction.StopActivityItem;
48 import android.content.Context;
49 import android.content.Intent;
50 import android.content.res.Configuration;
51 import android.content.res.Resources;
52 import android.graphics.Rect;
53 import android.hardware.display.DisplayManager;
54 import android.hardware.display.VirtualDisplay;
55 import android.os.Bundle;
56 import android.os.IBinder;
57 import android.platform.test.annotations.Presubmit;
58 import android.util.DisplayMetrics;
59 import android.util.MergedConfiguration;
60 import android.view.Display;
61 import android.view.View;
62 
63 import androidx.test.filters.FlakyTest;
64 import androidx.test.filters.MediumTest;
65 import androidx.test.platform.app.InstrumentationRegistry;
66 import androidx.test.rule.ActivityTestRule;
67 import androidx.test.runner.AndroidJUnit4;
68 
69 import com.android.internal.content.ReferrerIntent;
70 
71 import org.junit.After;
72 import org.junit.Test;
73 import org.junit.runner.RunWith;
74 
75 import java.util.ArrayList;
76 import java.util.List;
77 import java.util.concurrent.CountDownLatch;
78 import java.util.concurrent.TimeUnit;
79 import java.util.function.Consumer;
80 
81 /**
82  * Test for verifying {@link android.app.ActivityThread} class.
83  * Build/Install/Run:
84  *  atest FrameworksCoreTests:android.app.activity.ActivityThreadTest
85  */
86 @RunWith(AndroidJUnit4.class)
87 @MediumTest
88 @Presubmit
89 public class ActivityThreadTest {
90     private static final int TIMEOUT_SEC = 10;
91 
92     // The first sequence number to try with. Use a large number to avoid conflicts with the first a
93     // few sequence numbers the framework used to launch the test activity.
94     private static final int BASE_SEQ = 10000;
95 
96     private final ActivityTestRule<TestActivity> mActivityTestRule =
97             new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
98                     false /* launchActivity */);
99 
100     private ArrayList<VirtualDisplay> mCreatedVirtualDisplays;
101 
102     @After
tearDown()103     public void tearDown() {
104         if (mCreatedVirtualDisplays != null) {
105             mCreatedVirtualDisplays.forEach(VirtualDisplay::release);
106             mCreatedVirtualDisplays = null;
107         }
108     }
109 
110     @Test
testDoubleRelaunch()111     public void testDoubleRelaunch() throws Exception {
112         final Activity activity = mActivityTestRule.launchActivity(new Intent());
113         final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
114 
115         appThread.scheduleTransaction(newRelaunchResumeTransaction(activity));
116         appThread.scheduleTransaction(newRelaunchResumeTransaction(activity));
117         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
118     }
119 
120     @Test
testResumeAfterRelaunch()121     public void testResumeAfterRelaunch() throws Exception {
122         final Activity activity = mActivityTestRule.launchActivity(new Intent());
123         final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
124 
125         appThread.scheduleTransaction(newRelaunchResumeTransaction(activity));
126         appThread.scheduleTransaction(newResumeTransaction(activity));
127         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
128     }
129 
130     /** Verify that repeated resume requests to activity will be ignored. */
131     @Test
testRepeatedResume()132     public void testRepeatedResume() throws Exception {
133         final Activity activity = mActivityTestRule.launchActivity(new Intent());
134         final ActivityThread activityThread = activity.getActivityThread();
135         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
136             activityThread.executeTransaction(newResumeTransaction(activity));
137             final ActivityClientRecord r = getActivityClientRecord(activity);
138             assertFalse(activityThread.performResumeActivity(r, true /* finalStateRequest */,
139                     "test"));
140 
141             assertFalse(activityThread.performResumeActivity(r, false /* finalStateRequest */,
142                     "test"));
143         });
144     }
145 
146     /** Verify that custom intent set via Activity#setIntent() is preserved on relaunch. */
147     @Test
testCustomIntentPreservedOnRelaunch()148     public void testCustomIntentPreservedOnRelaunch() throws Exception {
149         final Intent initIntent = new Intent();
150         initIntent.setAction(ACTION_VIEW);
151         final Activity activity = mActivityTestRule.launchActivity(initIntent);
152         IBinder token = activity.getActivityToken();
153 
154         final ActivityThread activityThread = activity.getActivityThread();
155         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
156             // Recreate and check that intent is still the same.
157             activity.recreate();
158 
159             final Activity newActivity = activityThread.getActivity(token);
160             assertTrue("Original intent must be preserved after recreate",
161                     initIntent.filterEquals(newActivity.getIntent()));
162 
163             // Set custom intent, recreate and check if it is preserved.
164             final Intent customIntent = new Intent();
165             customIntent.setAction(ACTION_EDIT);
166             newActivity.setIntent(customIntent);
167 
168             activity.recreate();
169 
170             final Activity lastActivity = activityThread.getActivity(token);
171             assertTrue("Custom intent must be preserved after recreate",
172                     customIntent.filterEquals(lastActivity.getIntent()));
173         });
174     }
175 
176     @Test
testHandleActivityConfigurationChanged()177     public void testHandleActivityConfigurationChanged() {
178         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
179 
180         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
181             final int numOfConfig = activity.mNumOfConfigChanges;
182             applyConfigurationChange(activity, BASE_SEQ);
183             assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
184         });
185     }
186 
187     @Test
testRecreateActivity()188     public void testRecreateActivity() {
189         relaunchActivityAndAssertPreserveWindow(Activity::recreate);
190     }
191 
relaunchActivityAndAssertPreserveWindow(Consumer<Activity> relaunch)192     private void relaunchActivityAndAssertPreserveWindow(Consumer<Activity> relaunch) {
193         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
194         final ActivityThread activityThread = activity.getActivityThread();
195 
196         final IBinder[] token = new IBinder[1];
197         final View[] decorView = new View[1];
198 
199         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
200             token[0] = activity.getActivityToken();
201             decorView[0] = activity.getWindow().getDecorView();
202 
203             relaunch.accept(activity);
204         });
205 
206         final View[] newDecorView = new View[1];
207         final Activity[] newActivity = new Activity[1];
208 
209         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
210             newActivity[0] = activityThread.getActivity(token[0]);
211             newDecorView[0] = newActivity[0].getWindow().getDecorView();
212         });
213 
214         assertNotEquals("Activity must be relaunched", activity, newActivity[0]);
215         assertEquals("Window must be preserved", decorView[0], newDecorView[0]);
216     }
217 
218     @Test
testHandleActivityConfigurationChanged_DropStaleConfigurations()219     public void testHandleActivityConfigurationChanged_DropStaleConfigurations() {
220         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
221 
222         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
223             // Set the sequence number to BASE_SEQ.
224             applyConfigurationChange(activity, BASE_SEQ);
225 
226             final int orientation = activity.mConfig.orientation;
227             final int numOfConfig = activity.mNumOfConfigChanges;
228 
229             // Try to apply an old configuration change.
230             applyConfigurationChange(activity, BASE_SEQ - 1);
231             assertEquals(numOfConfig, activity.mNumOfConfigChanges);
232             assertEquals(orientation, activity.mConfig.orientation);
233         });
234     }
235 
236     @Test
testHandleActivityConfigurationChanged_ApplyNewConfigurations()237     public void testHandleActivityConfigurationChanged_ApplyNewConfigurations() {
238         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
239 
240         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
241             // Set the sequence number to BASE_SEQ and record the final sequence number it used.
242             final int seq = applyConfigurationChange(activity, BASE_SEQ);
243 
244             final int orientation = activity.mConfig.orientation;
245             final int numOfConfig = activity.mNumOfConfigChanges;
246 
247             // Try to apply an new configuration change.
248             applyConfigurationChange(activity, seq + 1);
249             assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
250             assertNotEquals(orientation, activity.mConfig.orientation);
251         });
252     }
253 
254     @Test
testHandleActivityConfigurationChanged_SkipWhenNewerConfigurationPending()255     public void testHandleActivityConfigurationChanged_SkipWhenNewerConfigurationPending() {
256         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
257 
258         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
259             // Set the sequence number to BASE_SEQ and record the final sequence number it used.
260             final int seq = applyConfigurationChange(activity, BASE_SEQ);
261 
262             final int orientation = activity.mConfig.orientation;
263             final int numOfConfig = activity.mNumOfConfigChanges;
264 
265             final ActivityThread activityThread = activity.getActivityThread();
266 
267             final Configuration newerConfig = new Configuration();
268             newerConfig.orientation = orientation == ORIENTATION_LANDSCAPE
269                     ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
270             newerConfig.seq = seq + 2;
271             final ActivityClientRecord r = getActivityClientRecord(activity);
272             activityThread.updatePendingActivityConfiguration(r, newerConfig);
273 
274             final Configuration olderConfig = new Configuration();
275             olderConfig.orientation = orientation;
276             olderConfig.seq = seq + 1;
277 
278             activityThread.handleActivityConfigurationChanged(r, olderConfig, INVALID_DISPLAY);
279             assertEquals(numOfConfig, activity.mNumOfConfigChanges);
280             assertEquals(olderConfig.orientation, activity.mConfig.orientation);
281 
282             activityThread.handleActivityConfigurationChanged(r, newerConfig, INVALID_DISPLAY);
283             assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
284             assertEquals(newerConfig.orientation, activity.mConfig.orientation);
285         });
286     }
287 
288     @Test
testHandleActivityConfigurationChanged_EnsureUpdatesProcessedInOrder()289     public void testHandleActivityConfigurationChanged_EnsureUpdatesProcessedInOrder()
290             throws Exception {
291         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
292 
293         final ActivityThread activityThread = activity.getActivityThread();
294         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
295             final Configuration config = new Configuration();
296             config.seq = BASE_SEQ;
297             config.orientation = ORIENTATION_PORTRAIT;
298 
299             activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity),
300                     config, INVALID_DISPLAY);
301         });
302 
303         final IApplicationThread appThread = activityThread.getApplicationThread();
304         final int numOfConfig = activity.mNumOfConfigChanges;
305 
306         final Configuration processConfigLandscape = new Configuration();
307         processConfigLandscape.orientation = ORIENTATION_LANDSCAPE;
308         processConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 60));
309         processConfigLandscape.seq = BASE_SEQ + 1;
310 
311         final Configuration activityConfigLandscape = new Configuration();
312         activityConfigLandscape.orientation = ORIENTATION_LANDSCAPE;
313         activityConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 50));
314         activityConfigLandscape.seq = BASE_SEQ + 2;
315 
316         final Configuration processConfigPortrait = new Configuration();
317         processConfigPortrait.orientation = ORIENTATION_PORTRAIT;
318         processConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 60, 100));
319         processConfigPortrait.seq = BASE_SEQ + 3;
320 
321         final Configuration activityConfigPortrait = new Configuration();
322         activityConfigPortrait.orientation = ORIENTATION_PORTRAIT;
323         activityConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 50, 100));
324         activityConfigPortrait.seq = BASE_SEQ + 4;
325 
326         activity.mConfigLatch = new CountDownLatch(1);
327         activity.mTestLatch = new CountDownLatch(1);
328 
329         ClientTransaction transaction = newTransaction(activityThread, null);
330         transaction.addCallback(ConfigurationChangeItem.obtain(processConfigLandscape));
331         appThread.scheduleTransaction(transaction);
332 
333         transaction = newTransaction(activityThread, activity.getActivityToken());
334         transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigLandscape));
335         transaction.addCallback(ConfigurationChangeItem.obtain(processConfigPortrait));
336         transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigPortrait));
337         appThread.scheduleTransaction(transaction);
338 
339         activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
340         activity.mConfigLatch.countDown();
341 
342         activity.mConfigLatch = null;
343         activity.mTestLatch = null;
344 
345         // Check display metrics, bounds should match the portrait activity bounds.
346         final Rect bounds = activity.getWindowManager().getCurrentWindowMetrics().getBounds();
347         assertEquals(activityConfigPortrait.windowConfiguration.getBounds(), bounds);
348 
349         // Ensure that Activity#onConfigurationChanged() not be called because the changes in
350         // WindowConfiguration shouldn't be reported, and we only apply the latest Configuration
351         // update in transaction.
352         assertEquals(numOfConfig, activity.mNumOfConfigChanges);
353     }
354 
355     @Test
testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration()356     public void testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration()
357             throws Exception {
358         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
359 
360         final ActivityThread activityThread = activity.getActivityThread();
361         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
362             final Configuration config = new Configuration();
363             config.seq = BASE_SEQ;
364             config.orientation = ORIENTATION_PORTRAIT;
365 
366             activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity),
367                     config, INVALID_DISPLAY);
368         });
369 
370         final int numOfConfig = activity.mNumOfConfigChanges;
371         final IApplicationThread appThread = activityThread.getApplicationThread();
372 
373         activity.mConfigLatch = new CountDownLatch(1);
374         activity.mTestLatch = new CountDownLatch(1);
375 
376         Configuration config = new Configuration();
377         config.seq = BASE_SEQ + 1;
378         config.orientation = ORIENTATION_LANDSCAPE;
379         appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
380 
381         // Wait until the main thread is performing the configuration change for the configuration
382         // with sequence number BASE_SEQ + 1 before proceeding. This is to mimic the situation where
383         // the activity takes very long time to process configuration changes.
384         activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
385 
386         config = new Configuration();
387         config.seq = BASE_SEQ + 2;
388         config.orientation = ORIENTATION_PORTRAIT;
389         appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
390 
391         config = new Configuration();
392         config.seq = BASE_SEQ + 3;
393         config.orientation = ORIENTATION_LANDSCAPE;
394         appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
395 
396         config = new Configuration();
397         config.seq = BASE_SEQ + 4;
398         config.orientation = ORIENTATION_PORTRAIT;
399         appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
400 
401         activity.mConfigLatch.countDown();
402         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
403 
404         activity.mConfigLatch = null;
405         activity.mTestLatch = null;
406 
407         // Only two more configuration changes: one with seq BASE_SEQ + 1; another with seq
408         // BASE_SEQ + 4. Configurations scheduled in between should be dropped.
409         assertEquals(numOfConfig + 2, activity.mNumOfConfigChanges);
410         assertEquals(ORIENTATION_PORTRAIT, activity.mConfig.orientation);
411     }
412 
413     @Test
testOrientationChanged_DoesntOverrideVirtualDisplayOrientation()414     public void testOrientationChanged_DoesntOverrideVirtualDisplayOrientation() {
415         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
416         final ActivityThread activityThread = activity.getActivityThread();
417 
418         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
419             Context appContext = activity.getApplication();
420             Configuration originalAppConfig =
421                     new Configuration(appContext.getResources().getConfiguration());
422 
423             int virtualDisplayWidth;
424             int virtualDisplayHeight;
425             if (originalAppConfig.orientation == ORIENTATION_PORTRAIT) {
426                 virtualDisplayWidth = 100;
427                 virtualDisplayHeight = 200;
428             } else {
429                 virtualDisplayWidth = 200;
430                 virtualDisplayHeight = 100;
431             }
432             final Display virtualDisplay = createVirtualDisplay(appContext,
433                     virtualDisplayWidth, virtualDisplayHeight);
434             Context virtualDisplayContext = appContext.createDisplayContext(virtualDisplay);
435             int originalVirtualDisplayOrientation = virtualDisplayContext.getResources()
436                     .getConfiguration().orientation;
437 
438 
439             // Perform global config change and verify there is no config change in derived display
440             // context.
441             Configuration newAppConfig = new Configuration(originalAppConfig);
442             newAppConfig.seq++;
443             newAppConfig.orientation = newAppConfig.orientation == ORIENTATION_PORTRAIT
444                     ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
445 
446             activityThread.updatePendingConfiguration(newAppConfig);
447             activityThread.handleConfigurationChanged(newAppConfig);
448 
449             try {
450                 assertEquals("Virtual display orientation must not change when process"
451                                 + " configuration orientation changes.",
452                         originalVirtualDisplayOrientation,
453                         virtualDisplayContext.getResources().getConfiguration().orientation);
454             } finally {
455                 // Make sure to reset the process config to prevent side effects to other
456                 // tests.
457                 Configuration activityThreadConfig = activityThread.getConfiguration();
458                 activityThreadConfig.seq = originalAppConfig.seq - 1;
459 
460                 Configuration resourceManagerConfig = ResourcesManager.getInstance()
461                         .getConfiguration();
462                 resourceManagerConfig.seq = originalAppConfig.seq - 1;
463 
464                 activityThread.updatePendingConfiguration(originalAppConfig);
465                 activityThread.handleConfigurationChanged(originalAppConfig);
466             }
467         });
468     }
469 
470     @Test
testActivityOrientationChanged_DoesntOverrideVirtualDisplayOrientation()471     public void testActivityOrientationChanged_DoesntOverrideVirtualDisplayOrientation() {
472         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
473         final ActivityThread activityThread = activity.getActivityThread();
474 
475         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
476             Configuration originalActivityConfig =
477                     new Configuration(activity.getResources().getConfiguration());
478 
479             int virtualDisplayWidth;
480             int virtualDisplayHeight;
481             if (originalActivityConfig.orientation == ORIENTATION_PORTRAIT) {
482                 virtualDisplayWidth = 100;
483                 virtualDisplayHeight = 200;
484             } else {
485                 virtualDisplayWidth = 200;
486                 virtualDisplayHeight = 100;
487             }
488             final Display virtualDisplay = createVirtualDisplay(activity,
489                     virtualDisplayWidth, virtualDisplayHeight);
490             Context virtualDisplayContext = activity.createDisplayContext(virtualDisplay);
491             int originalVirtualDisplayOrientation = virtualDisplayContext.getResources()
492                     .getConfiguration().orientation;
493 
494             // Perform activity config change and verify there is no config change in derived
495             // display context.
496             Configuration newActivityConfig = new Configuration(originalActivityConfig);
497             newActivityConfig.seq++;
498             newActivityConfig.orientation = newActivityConfig.orientation == ORIENTATION_PORTRAIT
499                     ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
500 
501             final ActivityClientRecord r = getActivityClientRecord(activity);
502             activityThread.updatePendingActivityConfiguration(r, newActivityConfig);
503             activityThread.handleActivityConfigurationChanged(r, newActivityConfig,
504                     INVALID_DISPLAY);
505 
506             assertEquals("Virtual display orientation must not change when activity"
507                             + " configuration orientation changes.",
508                     originalVirtualDisplayOrientation,
509                     virtualDisplayContext.getResources().getConfiguration().orientation);
510         });
511     }
512 
513     @Test
514     @FlakyTest(bugId = 176134235)
testHandleConfigurationChanged_DoesntOverrideActivityConfig()515     public void testHandleConfigurationChanged_DoesntOverrideActivityConfig() {
516         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
517 
518         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
519             final Configuration oldActivityConfig =
520                     new Configuration(activity.getResources().getConfiguration());
521             final DisplayMetrics oldActivityMetrics = new DisplayMetrics();
522             activity.getDisplay().getMetrics(oldActivityMetrics);
523             final Resources oldAppResources = activity.getApplication().getResources();
524             final Configuration oldAppConfig =
525                     new Configuration(oldAppResources.getConfiguration());
526             final DisplayMetrics oldApplicationMetrics = new DisplayMetrics();
527             oldApplicationMetrics.setTo(oldAppResources.getDisplayMetrics());
528             assertEquals("Process config must match the top activity config by default",
529                     0, oldActivityConfig.diffPublicOnly(oldAppConfig));
530             assertEquals("Process config must match the top activity config by default",
531                     oldActivityMetrics, oldApplicationMetrics);
532 
533             // Update the application configuration separately from activity config
534             final Configuration newAppConfig = new Configuration(oldAppConfig);
535             newAppConfig.densityDpi += 100;
536             newAppConfig.screenHeightDp += 100;
537             final Rect newBounds = new Rect(newAppConfig.windowConfiguration.getAppBounds());
538             newBounds.bottom += 100;
539             newAppConfig.windowConfiguration.setAppBounds(newBounds);
540             newAppConfig.windowConfiguration.setBounds(newBounds);
541             newAppConfig.seq++;
542 
543             final ActivityThread activityThread = activity.getActivityThread();
544             activityThread.handleConfigurationChanged(newAppConfig);
545 
546             // Verify that application config update was applied, but didn't change activity config.
547             assertEquals("Activity config must not change if the process config changes",
548                     oldActivityConfig, activity.getResources().getConfiguration());
549 
550             final DisplayMetrics newActivityMetrics = new DisplayMetrics();
551             activity.getDisplay().getMetrics(newActivityMetrics);
552             assertEquals("Activity display size must not change if the process config changes",
553                     oldActivityMetrics, newActivityMetrics);
554             final Resources newAppResources = activity.getApplication().getResources();
555             assertEquals("Application config must be updated",
556                     newAppConfig, newAppResources.getConfiguration());
557             final DisplayMetrics newApplicationMetrics = new DisplayMetrics();
558             newApplicationMetrics.setTo(newAppResources.getDisplayMetrics());
559             assertNotEquals("Application display size must be updated after config update",
560                     oldApplicationMetrics, newApplicationMetrics);
561             assertNotEquals("Application display size must be updated after config update",
562                     newActivityMetrics, newApplicationMetrics);
563         });
564     }
565 
566     @Test
testHandleProcessConfigurationChanged_DependOnProcessState()567     public void testHandleProcessConfigurationChanged_DependOnProcessState() {
568         final ActivityThread activityThread = ActivityThread.currentActivityThread();
569         final Configuration origConfig = activityThread.getConfiguration();
570         final int newDpi = origConfig.densityDpi + 10;
571         final Configuration newConfig = new Configuration(origConfig);
572         newConfig.seq++;
573         newConfig.densityDpi = newDpi;
574 
575         activityThread.updateProcessState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY,
576                 false /* fromIPC */);
577 
578         applyProcessConfiguration(activityThread, newConfig);
579         try {
580             // In the cached state, the configuration is only set as pending and not applied.
581             assertEquals(origConfig.densityDpi, activityThread.getConfiguration().densityDpi);
582             assertTrue(activityThread.isCachedProcessState());
583         } finally {
584             // The foreground state is the default state of instrumentation.
585             activityThread.updateProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
586                     false /* fromIPC */);
587         }
588         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
589 
590         try {
591             // The state becomes non-cached, the pending configuration should be applied.
592             assertEquals(newConfig.densityDpi, activityThread.getConfiguration().densityDpi);
593             assertFalse(activityThread.isCachedProcessState());
594         } finally {
595             // Restore to the original configuration.
596             activityThread.getConfiguration().seq = origConfig.seq - 1;
597             applyProcessConfiguration(activityThread, origConfig);
598         }
599     }
600 
applyProcessConfiguration(ActivityThread thread, Configuration config)601     private static void applyProcessConfiguration(ActivityThread thread, Configuration config) {
602         final ClientTransaction clientTransaction = newTransaction(thread,
603                 null /* activityToken */);
604         clientTransaction.addCallback(ConfigurationChangeItem.obtain(config));
605         final IApplicationThread appThread = thread.getApplicationThread();
606         try {
607             appThread.scheduleTransaction(clientTransaction);
608         } catch (Exception ignored) {
609         }
610         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
611     }
612 
613     @Test
testResumeAfterNewIntent()614     public void testResumeAfterNewIntent() {
615         final Activity activity = mActivityTestRule.launchActivity(new Intent());
616         final ActivityThread activityThread = activity.getActivityThread();
617         final ArrayList<ReferrerIntent> rIntents = new ArrayList<>();
618         rIntents.add(new ReferrerIntent(new Intent(), "android.app.activity"));
619 
620         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
621             activityThread.executeTransaction(newNewIntentTransaction(activity, rIntents, true));
622         });
623         assertThat(activity.isResumed()).isTrue();
624 
625         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
626             activityThread.executeTransaction(newStopTransaction(activity));
627             activityThread.executeTransaction(newNewIntentTransaction(activity, rIntents, false));
628         });
629         assertThat(activity.isResumed()).isFalse();
630     }
631 
632     @Test
testHandlePictureInPictureRequested_overriddenToEnter()633     public void testHandlePictureInPictureRequested_overriddenToEnter() {
634         final Intent startIntent = new Intent();
635         startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_ENTER, true);
636         final TestActivity activity = mActivityTestRule.launchActivity(startIntent);
637         final ActivityThread activityThread = activity.getActivityThread();
638         final ActivityClientRecord r = getActivityClientRecord(activity);
639 
640         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
641             activityThread.handlePictureInPictureRequested(r);
642         });
643 
644         assertTrue(activity.pipRequested());
645         assertTrue(activity.enteredPip());
646     }
647 
648     @Test
testHandlePictureInPictureRequested_overriddenToSkip()649     public void testHandlePictureInPictureRequested_overriddenToSkip() {
650         final Intent startIntent = new Intent();
651         startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_SKIP, true);
652         final TestActivity activity = mActivityTestRule.launchActivity(startIntent);
653         final ActivityThread activityThread = activity.getActivityThread();
654         final ActivityClientRecord r = getActivityClientRecord(activity);
655 
656         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
657             activityThread.handlePictureInPictureRequested(r);
658         });
659 
660         assertTrue(activity.pipRequested());
661         assertTrue(activity.enterPipSkipped());
662     }
663 
664     @Test
testHandlePictureInPictureRequested_notOverridden()665     public void testHandlePictureInPictureRequested_notOverridden() {
666         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
667         final ActivityThread activityThread = activity.getActivityThread();
668         final ActivityClientRecord r = getActivityClientRecord(activity);
669 
670         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
671             activityThread.handlePictureInPictureRequested(r);
672         });
673 
674         assertTrue(activity.pipRequested());
675         assertFalse(activity.enteredPip());
676         assertFalse(activity.enterPipSkipped());
677     }
678 
679     /**
680      * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
681      * Configuration, int)} to try to push activity configuration to the activity for the given
682      * sequence number.
683      * <p>
684      * It uses orientation to push the configuration and it tries a different orientation if the
685      * first attempt doesn't make through, to rule out the possibility that the previous
686      * configuration already has the same orientation.
687      *
688      * @param activity the test target activity
689      * @param seq the specified sequence number
690      * @return the sequence number this method tried with the last time, so that the caller can use
691      * the next sequence number for next configuration update.
692      */
applyConfigurationChange(TestActivity activity, int seq)693     private int applyConfigurationChange(TestActivity activity, int seq) {
694         final ActivityThread activityThread = activity.getActivityThread();
695         final ActivityClientRecord r = getActivityClientRecord(activity);
696 
697         final int numOfConfig = activity.mNumOfConfigChanges;
698         Configuration config = new Configuration();
699         config.orientation = ORIENTATION_PORTRAIT;
700         config.seq = seq;
701         activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY);
702 
703         if (activity.mNumOfConfigChanges > numOfConfig) {
704             return config.seq;
705         }
706 
707         config = new Configuration();
708         config.orientation = ORIENTATION_LANDSCAPE;
709         config.seq = seq + 1;
710         activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY);
711 
712         return config.seq;
713     }
714 
createVirtualDisplay(Context context, int w, int h)715     private Display createVirtualDisplay(Context context, int w, int h) {
716         final DisplayManager dm = context.getSystemService(DisplayManager.class);
717         final VirtualDisplay virtualDisplay = dm.createVirtualDisplay("virtual-display", w, h,
718                 200 /* densityDpi */, null /* surface */, 0 /* flags */);
719         if (mCreatedVirtualDisplays == null) {
720             mCreatedVirtualDisplays = new ArrayList<>();
721         }
722         mCreatedVirtualDisplays.add(virtualDisplay);
723         return virtualDisplay.getDisplay();
724     }
725 
getActivityClientRecord(Activity activity)726     private static ActivityClientRecord getActivityClientRecord(Activity activity) {
727         final ActivityThread thread = activity.getActivityThread();
728         final IBinder token = activity.getActivityToken();
729         return thread.getActivityClient(token);
730     }
731 
newRelaunchResumeTransaction(Activity activity)732     private static ClientTransaction newRelaunchResumeTransaction(Activity activity) {
733         final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null,
734                 null, 0, new MergedConfiguration(), false /* preserveWindow */);
735         final ResumeActivityItem resumeStateRequest =
736                 ResumeActivityItem.obtain(true /* isForward */);
737 
738         final ClientTransaction transaction = newTransaction(activity);
739         transaction.addCallback(callbackItem);
740         transaction.setLifecycleStateRequest(resumeStateRequest);
741 
742         return transaction;
743     }
744 
newResumeTransaction(Activity activity)745     private static ClientTransaction newResumeTransaction(Activity activity) {
746         final ResumeActivityItem resumeStateRequest =
747                 ResumeActivityItem.obtain(true /* isForward */);
748 
749         final ClientTransaction transaction = newTransaction(activity);
750         transaction.setLifecycleStateRequest(resumeStateRequest);
751 
752         return transaction;
753     }
754 
newStopTransaction(Activity activity)755     private static ClientTransaction newStopTransaction(Activity activity) {
756         final StopActivityItem stopStateRequest = StopActivityItem.obtain(0 /* configChanges */);
757 
758         final ClientTransaction transaction = newTransaction(activity);
759         transaction.setLifecycleStateRequest(stopStateRequest);
760 
761         return transaction;
762     }
763 
newActivityConfigTransaction(Activity activity, Configuration config)764     private static ClientTransaction newActivityConfigTransaction(Activity activity,
765             Configuration config) {
766         final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config);
767 
768         final ClientTransaction transaction = newTransaction(activity);
769         transaction.addCallback(item);
770 
771         return transaction;
772     }
773 
newNewIntentTransaction(Activity activity, List<ReferrerIntent> intents, boolean resume)774     private static ClientTransaction newNewIntentTransaction(Activity activity,
775             List<ReferrerIntent> intents, boolean resume) {
776         final NewIntentItem item = NewIntentItem.obtain(intents, resume);
777 
778         final ClientTransaction transaction = newTransaction(activity);
779         transaction.addCallback(item);
780 
781         return transaction;
782     }
783 
newTransaction(Activity activity)784     private static ClientTransaction newTransaction(Activity activity) {
785         return newTransaction(activity.getActivityThread(), activity.getActivityToken());
786     }
787 
newTransaction(ActivityThread activityThread, @Nullable IBinder activityToken)788     private static ClientTransaction newTransaction(ActivityThread activityThread,
789             @Nullable IBinder activityToken) {
790         return ClientTransaction.obtain(activityThread.getApplicationThread(), activityToken);
791     }
792 
793     // Test activity
794     public static class TestActivity extends Activity {
795         static final String PIP_REQUESTED_OVERRIDE_ENTER = "pip_requested_override_enter";
796         static final String PIP_REQUESTED_OVERRIDE_SKIP = "pip_requested_override_skip";
797 
798         int mNumOfConfigChanges;
799         final Configuration mConfig = new Configuration();
800 
801         private boolean mPipRequested;
802         private boolean mPipEntered;
803         private boolean mPipEnterSkipped;
804 
805         /**
806          * A latch used to notify tests that we're about to wait for configuration latch. This
807          * is used to notify test code that preExecute phase for activity configuration change
808          * transaction has passed.
809          */
810         volatile CountDownLatch mTestLatch;
811         /**
812          * If not {@code null} {@link #onConfigurationChanged(Configuration)} won't return until the
813          * latch reaches 0.
814          */
815         volatile CountDownLatch mConfigLatch;
816 
817         @Override
onCreate(Bundle savedInstanceState)818         protected void onCreate(Bundle savedInstanceState) {
819             super.onCreate(savedInstanceState);
820             getWindow().getDecorView().setKeepScreenOn(true);
821             setShowWhenLocked(true);
822             setTurnScreenOn(true);
823         }
824 
825         @Override
onConfigurationChanged(Configuration config)826         public void onConfigurationChanged(Configuration config) {
827             super.onConfigurationChanged(config);
828             mConfig.setTo(config);
829             ++mNumOfConfigChanges;
830 
831             if (mConfigLatch != null) {
832                 if (mTestLatch != null) {
833                     mTestLatch.countDown();
834                 }
835                 try {
836                     mConfigLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
837                 } catch (InterruptedException e) {
838                     throw new IllegalStateException(e);
839                 }
840             }
841         }
842 
843         @Override
onPictureInPictureRequested()844         public boolean onPictureInPictureRequested() {
845             mPipRequested = true;
846             if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_ENTER, false)) {
847                 enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
848                 mPipEntered = true;
849                 return true;
850             } else if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_SKIP, false)) {
851                 mPipEnterSkipped = true;
852                 return false;
853             }
854             return super.onPictureInPictureRequested();
855         }
856 
pipRequested()857         boolean pipRequested() {
858             return mPipRequested;
859         }
860 
enteredPip()861         boolean enteredPip() {
862             return mPipEntered;
863         }
864 
enterPipSkipped()865         boolean enterPipSkipped() {
866             return mPipEnterSkipped;
867         }
868     }
869 }
870