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