• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
25 import static android.view.Display.DEFAULT_DISPLAY;
26 import static android.view.Display.INVALID_DISPLAY;
27 
28 import static com.android.window.flags.Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG;
29 
30 import static com.google.common.truth.Truth.assertThat;
31 
32 import static org.junit.Assert.assertEquals;
33 import static org.junit.Assert.assertFalse;
34 import static org.junit.Assert.assertNotEquals;
35 import static org.junit.Assert.assertTrue;
36 import static org.mockito.ArgumentMatchers.any;
37 import static org.mockito.Mockito.atLeastOnce;
38 import static org.mockito.Mockito.clearInvocations;
39 import static org.mockito.Mockito.mock;
40 import static org.mockito.Mockito.never;
41 import static org.mockito.Mockito.spy;
42 import static org.mockito.Mockito.verify;
43 
44 import android.annotation.NonNull;
45 import android.app.Activity;
46 import android.app.ActivityThread;
47 import android.app.ActivityThread.ActivityClientRecord;
48 import android.app.Application;
49 import android.app.IApplicationThread;
50 import android.app.PictureInPictureParams;
51 import android.app.PictureInPictureUiState;
52 import android.app.ResourcesManager;
53 import android.app.servertransaction.ActivityConfigurationChangeItem;
54 import android.app.servertransaction.ActivityRelaunchItem;
55 import android.app.servertransaction.ClientTransaction;
56 import android.app.servertransaction.ClientTransactionItem;
57 import android.app.servertransaction.ClientTransactionListenerController;
58 import android.app.servertransaction.ConfigurationChangeItem;
59 import android.app.servertransaction.NewIntentItem;
60 import android.app.servertransaction.ResumeActivityItem;
61 import android.app.servertransaction.StopActivityItem;
62 import android.content.Context;
63 import android.content.Intent;
64 import android.content.res.CompatibilityInfo;
65 import android.content.res.Configuration;
66 import android.content.res.Resources;
67 import android.graphics.Rect;
68 import android.hardware.display.DisplayManager;
69 import android.hardware.display.VirtualDisplay;
70 import android.os.Bundle;
71 import android.os.IBinder;
72 import android.platform.test.annotations.Presubmit;
73 import android.platform.test.flag.junit.SetFlagsRule;
74 import android.util.DisplayMetrics;
75 import android.util.Log;
76 import android.util.MergedConfiguration;
77 import android.view.Display;
78 import android.view.View;
79 import android.window.ActivityWindowInfo;
80 import android.window.WindowContextInfo;
81 import android.window.WindowTokenClientController;
82 
83 import androidx.test.filters.MediumTest;
84 import androidx.test.platform.app.InstrumentationRegistry;
85 import androidx.test.rule.ActivityTestRule;
86 import androidx.test.runner.AndroidJUnit4;
87 
88 import com.android.internal.content.ReferrerIntent;
89 
90 import org.junit.After;
91 import org.junit.Before;
92 import org.junit.Rule;
93 import org.junit.Test;
94 import org.junit.runner.RunWith;
95 import org.mockito.MockitoAnnotations;
96 
97 import java.util.ArrayList;
98 import java.util.List;
99 import java.util.concurrent.CountDownLatch;
100 import java.util.concurrent.TimeUnit;
101 import java.util.function.BiConsumer;
102 import java.util.function.Consumer;
103 
104 /**
105  * Test for verifying {@link android.app.ActivityThread} class.
106  * Build/Install/Run:
107  *  atest FrameworksCoreTests:ActivityThreadTest
108  */
109 @RunWith(AndroidJUnit4.class)
110 @MediumTest
111 @Presubmit
112 public class ActivityThreadTest {
113 
114     private static final String TAG = "ActivityThreadTest";
115 
116     private static final int TIMEOUT_SEC = 10;
117 
118     // The first sequence number to try with. Use a large number to avoid conflicts with the first a
119     // few sequence numbers the framework used to launch the test activity.
120     private static final int BASE_SEQ = 10000000;
121 
122     @Rule(order = 0)
123     public final ActivityTestRule<TestActivity> mActivityTestRule =
124             new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
125                     false /* launchActivity */);
126 
127     @Rule(order = 1)
128     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
129 
130     private ActivityWindowInfoListener mActivityWindowInfoListener;
131     private WindowTokenClientController mOriginalWindowTokenClientController;
132     private Configuration mOriginalAppConfig;
133 
134     private ArrayList<VirtualDisplay> mCreatedVirtualDisplays;
135 
136     @Before
setup()137     public void setup() {
138         MockitoAnnotations.initMocks(this);
139 
140         // Keep track of the original controller, so that it can be used to restore in tearDown()
141         // when there is override in some test cases.
142         mOriginalWindowTokenClientController = WindowTokenClientController.getInstance();
143         mOriginalAppConfig = new Configuration(ActivityThread.currentActivityThread()
144                 .getConfiguration());
145         mActivityWindowInfoListener = spy(new ActivityWindowInfoListener());
146     }
147 
148     @After
tearDown()149     public void tearDown() {
150         if (mCreatedVirtualDisplays != null) {
151             mCreatedVirtualDisplays.forEach(VirtualDisplay::release);
152             mCreatedVirtualDisplays = null;
153         }
154         WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController);
155         ClientTransactionListenerController.getInstance()
156                 .unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
157         InstrumentationRegistry.getInstrumentation().runOnMainSync(
158                 () -> restoreConfig(ActivityThread.currentActivityThread(), mOriginalAppConfig));
159     }
160 
161     @Test
testTemporaryDirectory()162     public void testTemporaryDirectory() throws Exception {
163         assertEquals(System.getProperty("java.io.tmpdir"), System.getenv("TMPDIR"));
164     }
165 
166     @Test
testDoubleRelaunch()167     public void testDoubleRelaunch() throws Exception {
168         final Activity activity = mActivityTestRule.launchActivity(new Intent());
169         final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
170 
171         appThread.scheduleTransaction(newRelaunchResumeTransaction(activity));
172         appThread.scheduleTransaction(newRelaunchResumeTransaction(activity));
173         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
174     }
175 
176     @Test
testResumeAfterRelaunch()177     public void testResumeAfterRelaunch() throws Exception {
178         final Activity activity = mActivityTestRule.launchActivity(new Intent());
179         final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
180 
181         appThread.scheduleTransaction(newRelaunchResumeTransaction(activity));
182         appThread.scheduleTransaction(newResumeTransaction(activity));
183         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
184     }
185 
186     /** Verify that repeated resume requests to activity will be ignored. */
187     @Test
testRepeatedResume()188     public void testRepeatedResume() throws Exception {
189         final Activity activity = mActivityTestRule.launchActivity(new Intent());
190         final ActivityThread activityThread = activity.getActivityThread();
191         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
192             activityThread.executeTransaction(newResumeTransaction(activity));
193             final ActivityClientRecord r = getActivityClientRecord(activity);
194             assertFalse(activityThread.performResumeActivity(r, true /* finalStateRequest */,
195                     "test"));
196 
197             assertFalse(activityThread.performResumeActivity(r, false /* finalStateRequest */,
198                     "test"));
199         });
200     }
201 
202     /** Verify that custom intent set via Activity#setIntent() is preserved on relaunch. */
203     @Test
testCustomIntentPreservedOnRelaunch()204     public void testCustomIntentPreservedOnRelaunch() throws Exception {
205         final Intent initIntent = new Intent();
206         initIntent.setAction(ACTION_VIEW);
207         final Activity activity = mActivityTestRule.launchActivity(initIntent);
208         IBinder token = activity.getActivityToken();
209 
210         final ActivityThread activityThread = activity.getActivityThread();
211         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
212             // Recreate and check that intent is still the same.
213             activity.recreate();
214 
215             final Activity newActivity = activityThread.getActivity(token);
216             assertTrue("Original intent must be preserved after recreate",
217                     initIntent.filterEquals(newActivity.getIntent()));
218 
219             // Set custom intent, recreate and check if it is preserved.
220             final Intent customIntent = new Intent();
221             customIntent.setAction(ACTION_EDIT);
222             newActivity.setIntent(customIntent);
223 
224             activity.recreate();
225 
226             final Activity lastActivity = activityThread.getActivity(token);
227             assertTrue("Custom intent must be preserved after recreate",
228                     customIntent.filterEquals(lastActivity.getIntent()));
229         });
230     }
231 
232     @Test
testOverrideScale()233     public void testOverrideScale() throws Exception {
234         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
235         final Application app = activity.getApplication();
236         final ActivityThread activityThread = activity.getActivityThread();
237         final IApplicationThread appThread = activityThread.getApplicationThread();
238         final DisplayMetrics originalAppMetrics = new DisplayMetrics();
239         originalAppMetrics.setTo(app.getResources().getDisplayMetrics());
240         final Configuration originalAppConfig =
241                 new Configuration(app.getResources().getConfiguration());
242         final DisplayMetrics originalActivityMetrics = new DisplayMetrics();
243         originalActivityMetrics.setTo(activity.getResources().getDisplayMetrics());
244         final Configuration originalActivityConfig =
245                 new Configuration(activity.getResources().getConfiguration());
246 
247         final Configuration newConfig = new Configuration(originalAppConfig);
248         newConfig.seq = BASE_SEQ + 1;
249         newConfig.smallestScreenWidthDp++;
250 
251         final float originalScale = CompatibilityInfo.getOverrideInvertedScale();
252         float scale = 0.5f;
253         CompatibilityInfo.setOverrideInvertedScale(scale);
254         try {
255             // Send process level config change.
256             ClientTransaction transaction = newTransaction(activityThread);
257             transaction.addTransactionItem(ConfigurationChangeItem.obtain(
258                     newConfig, DEVICE_ID_INVALID));
259             appThread.scheduleTransaction(transaction);
260             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
261 
262             assertScreenScale(scale, app, originalAppConfig, originalAppMetrics);
263             // The activity's config doesn't change because ConfigurationChangeItem is process level
264             // that won't affect activity's override config.
265             assertEquals(originalActivityConfig.densityDpi,
266                     activity.getResources().getConfiguration().densityDpi);
267 
268             scale = 0.8f;
269             CompatibilityInfo.setOverrideInvertedScale(scale);
270             // Send activity level config change.
271             newConfig.seq++;
272             newConfig.smallestScreenWidthDp++;
273             transaction = newTransaction(activityThread);
274             transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
275                     activity.getActivityToken(), newConfig, new ActivityWindowInfo()));
276             appThread.scheduleTransaction(transaction);
277             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
278 
279             assertScreenScale(scale, activity, originalActivityConfig, originalActivityMetrics);
280 
281             // Execute a local relaunch item with current scaled config (e.g. simulate recreate),
282             // the config should not be scaled again.
283             InstrumentationRegistry.getInstrumentation().runOnMainSync(
284                     () -> activityThread.executeTransaction(
285                             newRelaunchResumeTransaction(activity)));
286 
287             assertScreenScale(scale, activity, originalActivityConfig, originalActivityMetrics);
288         } finally {
289             CompatibilityInfo.setOverrideInvertedScale(originalScale);
290             InstrumentationRegistry.getInstrumentation().runOnMainSync(
291                     () -> restoreConfig(activityThread, originalAppConfig));
292         }
293         assertScreenScale(originalScale, app, originalAppConfig, originalAppMetrics);
294     }
295 
assertScreenScale(float scale, Context context, Configuration origConfig, DisplayMetrics origMetrics)296     private static void assertScreenScale(float scale, Context context,
297             Configuration origConfig, DisplayMetrics origMetrics) {
298         final int expectedDpi = (int) (origConfig.densityDpi * scale + .5f);
299         final float expectedDensity = origMetrics.density * scale;
300         final int expectedWidthPixels = (int) (origMetrics.widthPixels * scale + .5f);
301         final int expectedHeightPixels = (int) (origMetrics.heightPixels * scale + .5f);
302         final Configuration expectedConfig = new Configuration(origConfig);
303         CompatibilityInfo.scaleConfiguration(scale, expectedConfig);
304         final Rect expectedBounds = expectedConfig.windowConfiguration.getBounds();
305         final Rect expectedAppBounds = expectedConfig.windowConfiguration.getAppBounds();
306         final Rect expectedMaxBounds = expectedConfig.windowConfiguration.getMaxBounds();
307 
308         final Configuration currentConfig = context.getResources().getConfiguration();
309         final DisplayMetrics currentMetrics = context.getResources().getDisplayMetrics();
310         assertEquals(expectedDpi, currentConfig.densityDpi);
311         assertEquals(expectedDpi, currentMetrics.densityDpi);
312         assertEquals(expectedDensity, currentMetrics.density, 0.001f);
313         assertEquals(expectedWidthPixels, currentMetrics.widthPixels);
314         assertEquals(expectedHeightPixels, currentMetrics.heightPixels);
315         assertEquals(expectedBounds, currentConfig.windowConfiguration.getBounds());
316         assertEquals(expectedAppBounds, currentConfig.windowConfiguration.getAppBounds());
317         assertEquals(expectedMaxBounds, currentConfig.windowConfiguration.getMaxBounds());
318     }
319 
320     @Test
testHandleActivityConfigurationChanged()321     public void testHandleActivityConfigurationChanged() {
322         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
323 
324         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
325             final int numOfConfig = activity.mNumOfConfigChanges;
326             applyConfigurationChange(activity, BASE_SEQ);
327             assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
328         });
329     }
330 
331     @Test
testRecreateActivity()332     public void testRecreateActivity() {
333         relaunchActivityAndAssertPreserveWindow(Activity::recreate);
334     }
335 
relaunchActivityAndAssertPreserveWindow(Consumer<Activity> relaunch)336     private void relaunchActivityAndAssertPreserveWindow(Consumer<Activity> relaunch) {
337         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
338         final ActivityThread activityThread = activity.getActivityThread();
339 
340         final IBinder[] token = new IBinder[1];
341         final View[] decorView = new View[1];
342 
343         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
344             token[0] = activity.getActivityToken();
345             decorView[0] = activity.getWindow().getDecorView();
346 
347             relaunch.accept(activity);
348         });
349 
350         final View[] newDecorView = new View[1];
351         final Activity[] newActivity = new Activity[1];
352 
353         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
354             newActivity[0] = activityThread.getActivity(token[0]);
355             newDecorView[0] = newActivity[0].getWindow().getDecorView();
356         });
357 
358         assertNotEquals("Activity must be relaunched", activity, newActivity[0]);
359         assertEquals("Window must be preserved", decorView[0], newDecorView[0]);
360     }
361 
362     @Test
testHandleActivityConfigurationChanged_DropStaleConfigurations()363     public void testHandleActivityConfigurationChanged_DropStaleConfigurations() {
364         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
365 
366         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
367             // Set the sequence number to BASE_SEQ.
368             applyConfigurationChange(activity, BASE_SEQ);
369 
370             final int orientation = activity.mConfig.orientation;
371             final int numOfConfig = activity.mNumOfConfigChanges;
372 
373             // Try to apply an old configuration change.
374             applyConfigurationChange(activity, BASE_SEQ - 1);
375             assertEquals(numOfConfig, activity.mNumOfConfigChanges);
376             assertEquals(orientation, activity.mConfig.orientation);
377         });
378     }
379 
380     @Test
testHandleActivityConfigurationChanged_ApplyNewConfigurations()381     public void testHandleActivityConfigurationChanged_ApplyNewConfigurations() {
382         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
383 
384         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
385             // Set the sequence number to BASE_SEQ and record the final sequence number it used.
386             final int seq = applyConfigurationChange(activity, BASE_SEQ);
387 
388             final int orientation = activity.mConfig.orientation;
389             final int numOfConfig = activity.mNumOfConfigChanges;
390 
391             // Try to apply an new configuration change.
392             applyConfigurationChange(activity, seq + 1);
393             assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
394             assertNotEquals(orientation, activity.mConfig.orientation);
395         });
396     }
397 
398     @Test
testHandleActivityConfigurationChanged_SkipWhenNewerConfigurationPending()399     public void testHandleActivityConfigurationChanged_SkipWhenNewerConfigurationPending() {
400         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
401 
402         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
403             // Set the sequence number to BASE_SEQ and record the final sequence number it used.
404             final int seq = applyConfigurationChange(activity, BASE_SEQ);
405 
406             final int orientation = activity.mConfig.orientation;
407             final int numOfConfig = activity.mNumOfConfigChanges;
408 
409             final ActivityThread activityThread = activity.getActivityThread();
410 
411             final Configuration newerConfig = new Configuration();
412             newerConfig.orientation = orientation == ORIENTATION_LANDSCAPE
413                     ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
414             newerConfig.seq = seq + 2;
415             activityThread.updatePendingActivityConfiguration(activity.getActivityToken(),
416                     newerConfig);
417 
418             final Configuration olderConfig = new Configuration();
419             olderConfig.orientation = orientation;
420             olderConfig.seq = seq + 1;
421 
422             final ActivityClientRecord r = getActivityClientRecord(activity);
423             activityThread.handleActivityConfigurationChanged(r, olderConfig, INVALID_DISPLAY,
424                     new ActivityWindowInfo());
425             assertEquals(numOfConfig, activity.mNumOfConfigChanges);
426             assertEquals(olderConfig.orientation, activity.mConfig.orientation);
427 
428             activityThread.handleActivityConfigurationChanged(r, newerConfig, INVALID_DISPLAY,
429                     new ActivityWindowInfo());
430             assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
431             assertEquals(newerConfig.orientation, activity.mConfig.orientation);
432         });
433     }
434 
435     @Test
testHandleActivityConfigurationChanged_EnsureUpdatesProcessedInOrder()436     public void testHandleActivityConfigurationChanged_EnsureUpdatesProcessedInOrder()
437             throws Exception {
438         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
439 
440         final ActivityThread activityThread = activity.getActivityThread();
441         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
442             final Configuration config = new Configuration();
443             config.seq = BASE_SEQ;
444             config.orientation = ORIENTATION_PORTRAIT;
445 
446             activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity),
447                     config, INVALID_DISPLAY, new ActivityWindowInfo());
448         });
449 
450         final IApplicationThread appThread = activityThread.getApplicationThread();
451         final int numOfConfig = activity.mNumOfConfigChanges;
452 
453         final Configuration processConfigLandscape = new Configuration();
454         processConfigLandscape.orientation = ORIENTATION_LANDSCAPE;
455         processConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 60));
456         processConfigLandscape.seq = BASE_SEQ + 1;
457 
458         final Configuration activityConfigLandscape = new Configuration();
459         activityConfigLandscape.orientation = ORIENTATION_LANDSCAPE;
460         activityConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 50));
461         activityConfigLandscape.seq = BASE_SEQ + 2;
462 
463         final Configuration processConfigPortrait = new Configuration();
464         processConfigPortrait.orientation = ORIENTATION_PORTRAIT;
465         processConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 60, 100));
466         processConfigPortrait.seq = BASE_SEQ + 3;
467 
468         final Configuration activityConfigPortrait = new Configuration();
469         activityConfigPortrait.orientation = ORIENTATION_PORTRAIT;
470         activityConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 50, 100));
471         activityConfigPortrait.seq = BASE_SEQ + 4;
472 
473         activity.mConfigLatch = new CountDownLatch(1);
474         activity.mTestLatch = new CountDownLatch(1);
475 
476         ClientTransaction transaction = newTransaction(activityThread);
477         transaction.addTransactionItem(ConfigurationChangeItem.obtain(
478                 processConfigLandscape, DEVICE_ID_INVALID));
479         appThread.scheduleTransaction(transaction);
480 
481         transaction = newTransaction(activityThread);
482         transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
483                 activity.getActivityToken(), activityConfigLandscape, new ActivityWindowInfo()));
484         transaction.addTransactionItem(ConfigurationChangeItem.obtain(
485                 processConfigPortrait, DEVICE_ID_INVALID));
486         transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
487                 activity.getActivityToken(), activityConfigPortrait, new ActivityWindowInfo()));
488         appThread.scheduleTransaction(transaction);
489 
490         activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
491         activity.mConfigLatch.countDown();
492 
493         activity.mConfigLatch = null;
494         activity.mTestLatch = null;
495 
496         // Check display metrics, bounds should match the portrait activity bounds.
497         final Rect bounds = activity.getWindowManager().getCurrentWindowMetrics().getBounds();
498         assertEquals(activityConfigPortrait.windowConfiguration.getBounds(), bounds);
499 
500         // Ensure that Activity#onConfigurationChanged() not be called because the changes in
501         // WindowConfiguration shouldn't be reported, and we only apply the latest Configuration
502         // update in transaction.
503         assertEquals(numOfConfig, activity.mNumOfConfigChanges);
504     }
505 
506     @Test
testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration()507     public void testHandleActivityConfigurationChanged_OnlyAppliesNewestConfiguration()
508             throws Exception {
509         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
510 
511         final ActivityThread activityThread = activity.getActivityThread();
512         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
513             final Configuration config = new Configuration();
514             config.seq = BASE_SEQ;
515             config.orientation = ORIENTATION_PORTRAIT;
516 
517             activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity),
518                     config, INVALID_DISPLAY, new ActivityWindowInfo());
519         });
520 
521         final int numOfConfig = activity.mNumOfConfigChanges;
522         final IApplicationThread appThread = activityThread.getApplicationThread();
523 
524         activity.mConfigLatch = new CountDownLatch(1);
525         activity.mTestLatch = new CountDownLatch(1);
526 
527         Configuration config = new Configuration();
528         config.seq = BASE_SEQ + 1;
529         config.orientation = ORIENTATION_LANDSCAPE;
530         appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
531 
532         // Wait until the main thread is performing the configuration change for the configuration
533         // with sequence number BASE_SEQ + 1 before proceeding. This is to mimic the situation where
534         // the activity takes very long time to process configuration changes.
535         activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
536 
537         config = new Configuration();
538         config.seq = BASE_SEQ + 2;
539         config.orientation = ORIENTATION_PORTRAIT;
540         appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
541 
542         config = new Configuration();
543         config.seq = BASE_SEQ + 3;
544         config.orientation = ORIENTATION_LANDSCAPE;
545         appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
546 
547         config = new Configuration();
548         config.seq = BASE_SEQ + 4;
549         config.orientation = ORIENTATION_PORTRAIT;
550         appThread.scheduleTransaction(newActivityConfigTransaction(activity, config));
551 
552         activity.mConfigLatch.countDown();
553         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
554 
555         activity.mConfigLatch = null;
556         activity.mTestLatch = null;
557 
558         // Only two more configuration changes: one with seq BASE_SEQ + 1; another with seq
559         // BASE_SEQ + 4. Configurations scheduled in between should be dropped.
560         assertEquals(numOfConfig + 2, activity.mNumOfConfigChanges);
561         assertEquals(ORIENTATION_PORTRAIT, activity.mConfig.orientation);
562     }
563 
564     @Test
testOrientationChanged_DoesntOverrideVirtualDisplayOrientation()565     public void testOrientationChanged_DoesntOverrideVirtualDisplayOrientation() {
566         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
567         final ActivityThread activityThread = activity.getActivityThread();
568 
569         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
570             Context appContext = activity.getApplication();
571             Configuration originalAppConfig =
572                     new Configuration(appContext.getResources().getConfiguration());
573 
574             int virtualDisplayWidth;
575             int virtualDisplayHeight;
576             if (originalAppConfig.orientation == ORIENTATION_PORTRAIT) {
577                 virtualDisplayWidth = 100;
578                 virtualDisplayHeight = 200;
579             } else {
580                 virtualDisplayWidth = 200;
581                 virtualDisplayHeight = 100;
582             }
583             final Display virtualDisplay = createVirtualDisplay(appContext,
584                     virtualDisplayWidth, virtualDisplayHeight);
585             Context virtualDisplayContext = appContext.createDisplayContext(virtualDisplay);
586             int originalVirtualDisplayOrientation = virtualDisplayContext.getResources()
587                     .getConfiguration().orientation;
588 
589 
590             // Perform global config change and verify there is no config change in derived display
591             // context.
592             Configuration newAppConfig = new Configuration(originalAppConfig);
593             newAppConfig.seq++;
594             newAppConfig.orientation = newAppConfig.orientation == ORIENTATION_PORTRAIT
595                     ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
596 
597             activityThread.updatePendingConfiguration(newAppConfig);
598             activityThread.handleConfigurationChanged(newAppConfig, DEVICE_ID_INVALID);
599 
600             assertEquals("Virtual display orientation must not change when process"
601                             + " configuration orientation changes.",
602                     originalVirtualDisplayOrientation,
603                     virtualDisplayContext.getResources().getConfiguration().orientation);
604         });
605     }
606 
restoreConfig(ActivityThread thread, Configuration originalConfig)607     private static void restoreConfig(ActivityThread thread, Configuration originalConfig) {
608         thread.getConfiguration().seq = originalConfig.seq - 1;
609         ResourcesManager.getInstance().getConfiguration().seq = originalConfig.seq - 1;
610         thread.handleConfigurationChanged(originalConfig, DEVICE_ID_INVALID);
611     }
612 
613     @Test
testActivityOrientationChanged_DoesntOverrideVirtualDisplayOrientation()614     public void testActivityOrientationChanged_DoesntOverrideVirtualDisplayOrientation() {
615         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
616         final ActivityThread activityThread = activity.getActivityThread();
617 
618         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
619             Configuration originalActivityConfig =
620                     new Configuration(activity.getResources().getConfiguration());
621 
622             int virtualDisplayWidth;
623             int virtualDisplayHeight;
624             if (originalActivityConfig.orientation == ORIENTATION_PORTRAIT) {
625                 virtualDisplayWidth = 100;
626                 virtualDisplayHeight = 200;
627             } else {
628                 virtualDisplayWidth = 200;
629                 virtualDisplayHeight = 100;
630             }
631             final Display virtualDisplay = createVirtualDisplay(activity,
632                     virtualDisplayWidth, virtualDisplayHeight);
633             Context virtualDisplayContext = activity.createDisplayContext(virtualDisplay);
634             int originalVirtualDisplayOrientation = virtualDisplayContext.getResources()
635                     .getConfiguration().orientation;
636 
637             // Perform activity config change and verify there is no config change in derived
638             // display context.
639             Configuration newActivityConfig = new Configuration(originalActivityConfig);
640             newActivityConfig.seq++;
641             newActivityConfig.orientation = newActivityConfig.orientation == ORIENTATION_PORTRAIT
642                     ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
643 
644             final ActivityClientRecord r = getActivityClientRecord(activity);
645             activityThread.updatePendingActivityConfiguration(activity.getActivityToken(),
646                     newActivityConfig);
647             activityThread.handleActivityConfigurationChanged(r, newActivityConfig,
648                     INVALID_DISPLAY, new ActivityWindowInfo());
649 
650             assertEquals("Virtual display orientation must not change when activity"
651                             + " configuration orientation changes.",
652                     originalVirtualDisplayOrientation,
653                     virtualDisplayContext.getResources().getConfiguration().orientation);
654         });
655     }
656 
657     @Test
testHandleConfigurationChanged_DoesntOverrideActivityConfig()658     public void testHandleConfigurationChanged_DoesntOverrideActivityConfig() {
659         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
660 
661         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
662             final Configuration oldActivityConfig =
663                     new Configuration(activity.getResources().getConfiguration());
664             final DisplayMetrics oldActivityMetrics = new DisplayMetrics();
665             activity.getDisplay().getMetrics(oldActivityMetrics);
666             final Resources oldAppResources = activity.getApplication().getResources();
667             final Configuration oldAppConfig =
668                     new Configuration(oldAppResources.getConfiguration());
669             final DisplayMetrics oldApplicationMetrics = new DisplayMetrics();
670             oldApplicationMetrics.setTo(oldAppResources.getDisplayMetrics());
671             assertEquals("Process config must match the top activity config by default"
672                     + ", activity=" + oldActivityConfig + ", app=" + oldAppConfig,
673                     0, oldActivityConfig.diffPublicOnly(oldAppConfig));
674             assertEquals("Process config must match the top activity config by default",
675                     oldActivityMetrics, oldApplicationMetrics);
676 
677             // Update the application configuration separately from activity config
678             final Configuration newAppConfig = new Configuration(oldAppConfig);
679             newAppConfig.densityDpi += 100;
680             newAppConfig.screenHeightDp += 100;
681             final Rect newBounds = new Rect(newAppConfig.windowConfiguration.getAppBounds());
682             newBounds.bottom += 100;
683             newAppConfig.windowConfiguration.setAppBounds(newBounds);
684             newAppConfig.windowConfiguration.setBounds(newBounds);
685             newAppConfig.seq++;
686 
687             final ActivityThread activityThread = activity.getActivityThread();
688             activityThread.handleConfigurationChanged(newAppConfig, DEVICE_ID_INVALID);
689 
690             // Verify that application config update was applied, but didn't change activity config.
691             assertEquals("Activity config must not change if the process config changes",
692                     oldActivityConfig, activity.getResources().getConfiguration());
693 
694             final DisplayMetrics newActivityMetrics = new DisplayMetrics();
695             activity.getDisplay().getMetrics(newActivityMetrics);
696             assertEquals("Activity display size must not change if the process config changes",
697                     oldActivityMetrics, newActivityMetrics);
698             final Resources newAppResources = activity.getApplication().getResources();
699             assertEquals("Application config must be updated",
700                     newAppConfig, newAppResources.getConfiguration());
701             final DisplayMetrics newApplicationMetrics = new DisplayMetrics();
702             newApplicationMetrics.setTo(newAppResources.getDisplayMetrics());
703             assertNotEquals("Application display size must be updated after config update",
704                     oldApplicationMetrics, newApplicationMetrics);
705             assertNotEquals("Application display size must be updated after config update",
706                     newActivityMetrics, newApplicationMetrics);
707         });
708     }
709 
710     @Test
testResumeAfterNewIntent()711     public void testResumeAfterNewIntent() {
712         final Activity activity = mActivityTestRule.launchActivity(new Intent());
713         final ActivityThread activityThread = activity.getActivityThread();
714         final ArrayList<ReferrerIntent> rIntents = new ArrayList<>();
715         rIntents.add(new ReferrerIntent(new Intent(), "android.app.activity"));
716 
717         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
718             activityThread.executeTransaction(newNewIntentTransaction(activity, rIntents, true));
719         });
720         assertThat(activity.isResumed()).isTrue();
721 
722         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
723             activityThread.executeTransaction(newStopTransaction(activity));
724             activityThread.executeTransaction(newNewIntentTransaction(activity, rIntents, false));
725         });
726         assertThat(activity.isResumed()).isFalse();
727     }
728 
729     @Test
testHandlePictureInPictureRequested_overriddenToEnter()730     public void testHandlePictureInPictureRequested_overriddenToEnter() {
731         final Intent startIntent = new Intent();
732         startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_ENTER, true);
733         final TestActivity activity = mActivityTestRule.launchActivity(startIntent);
734         final ActivityThread activityThread = activity.getActivityThread();
735         final ActivityClientRecord r = getActivityClientRecord(activity);
736         if (android.app.Flags.enablePipUiStateCallbackOnEntering()) {
737             activity.mPipUiStateLatch = new CountDownLatch(1);
738         }
739 
740         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
741             activityThread.handlePictureInPictureRequested(r);
742         });
743 
744         assertTrue(activity.pipRequested());
745         assertTrue(activity.enteredPip());
746     }
747 
748     @Test
testHandlePictureInPictureRequested_overriddenToSkip()749     public void testHandlePictureInPictureRequested_overriddenToSkip() {
750         final Intent startIntent = new Intent();
751         startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_SKIP, true);
752         final TestActivity activity = mActivityTestRule.launchActivity(startIntent);
753         final ActivityThread activityThread = activity.getActivityThread();
754         final ActivityClientRecord r = getActivityClientRecord(activity);
755 
756         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
757             activityThread.handlePictureInPictureRequested(r);
758         });
759 
760         assertTrue(activity.pipRequested());
761         assertTrue(activity.enterPipSkipped());
762     }
763 
764     @Test
testHandlePictureInPictureRequested_notOverridden()765     public void testHandlePictureInPictureRequested_notOverridden() {
766         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
767         final ActivityThread activityThread = activity.getActivityThread();
768         final ActivityClientRecord r = getActivityClientRecord(activity);
769 
770         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
771             activityThread.handlePictureInPictureRequested(r);
772         });
773 
774         assertTrue(activity.pipRequested());
775         assertFalse(activity.enteredPip());
776         assertFalse(activity.enterPipSkipped());
777     }
778 
779     @Test
testHandleWindowContextConfigurationChanged()780     public void testHandleWindowContextConfigurationChanged() {
781         final Activity activity = mActivityTestRule.launchActivity(new Intent());
782         final ActivityThread activityThread = activity.getActivityThread();
783         final WindowTokenClientController windowTokenClientController =
784                 mock(WindowTokenClientController.class);
785         WindowTokenClientController.overrideForTesting(windowTokenClientController);
786         final IBinder clientToken = mock(IBinder.class);
787         final Configuration configuration = new Configuration();
788         final WindowContextInfo info = new WindowContextInfo(configuration, DEFAULT_DISPLAY);
789 
790         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> activityThread
791                 .handleWindowContextInfoChanged(clientToken, info));
792 
793         verify(windowTokenClientController).onWindowContextInfoChanged(clientToken, info);
794     }
795 
796     @Test
testHandleWindowContextWindowRemoval()797     public void testHandleWindowContextWindowRemoval() {
798         final Activity activity = mActivityTestRule.launchActivity(new Intent());
799         final ActivityThread activityThread = activity.getActivityThread();
800         final WindowTokenClientController windowTokenClientController =
801                 mock(WindowTokenClientController.class);
802         WindowTokenClientController.overrideForTesting(windowTokenClientController);
803         final IBinder clientToken = mock(IBinder.class);
804 
805         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> activityThread
806                 .handleWindowContextWindowRemoval(clientToken));
807 
808         verify(windowTokenClientController).onWindowContextWindowRemoved(clientToken);
809     }
810 
811     @Test
testActivityWindowInfoChanged_activityLaunch()812     public void testActivityWindowInfoChanged_activityLaunch() {
813         mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
814         ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
815                 mActivityWindowInfoListener);
816 
817         final Activity activity = mActivityTestRule.launchActivity(new Intent());
818         mActivityWindowInfoListener.await();
819         final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);
820 
821         // In case the system change the window after launch, there can be more than one callback.
822         verify(mActivityWindowInfoListener, atLeastOnce()).accept(activityClientRecord.token,
823                 activityClientRecord.getActivityWindowInfo());
824     }
825 
826     @Test
testActivityWindowInfoChanged_activityRelaunch()827     public void testActivityWindowInfoChanged_activityRelaunch() {
828         mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
829         ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
830                 mActivityWindowInfoListener);
831 
832         final Activity activity = mActivityTestRule.launchActivity(new Intent());
833         mActivityWindowInfoListener.await();
834         final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);
835 
836         // Run on main thread to avoid racing from updating from window relayout.
837         final ActivityThread activityThread = activity.getActivityThread();
838         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
839             // Try relaunch with the same ActivityWindowInfo
840             clearInvocations(mActivityWindowInfoListener);
841             activityThread.executeTransaction(newRelaunchResumeTransaction(activity));
842 
843             // The same ActivityWindowInfo won't trigger duplicated callback.
844             verify(mActivityWindowInfoListener, never()).accept(activityClientRecord.token,
845                     activityClientRecord.getActivityWindowInfo());
846 
847             // Try relaunch with different ActivityWindowInfo
848             final Configuration currentConfig = activity.getResources().getConfiguration();
849             final ActivityWindowInfo newInfo = new ActivityWindowInfo();
850             newInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
851                     new Rect(0, 0, 1000, 1000));
852             final ActivityRelaunchItem relaunchItem = ActivityRelaunchItem.obtain(
853                     activity.getActivityToken(), null, null, 0,
854                     new MergedConfiguration(currentConfig, currentConfig),
855                     false /* preserveWindow */, newInfo);
856             final ClientTransaction transaction = newTransaction(activity);
857             transaction.addTransactionItem(relaunchItem);
858 
859             clearInvocations(mActivityWindowInfoListener);
860             activityThread.executeTransaction(transaction);
861 
862             // Trigger callback with a different ActivityWindowInfo
863             verify(mActivityWindowInfoListener).accept(activityClientRecord.token, newInfo);
864         });
865     }
866 
867     @Test
testActivityWindowInfoChanged_activityConfigurationChanged()868     public void testActivityWindowInfoChanged_activityConfigurationChanged() {
869         mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
870         ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
871                 mActivityWindowInfoListener);
872 
873         final Activity activity = mActivityTestRule.launchActivity(new Intent());
874         mActivityWindowInfoListener.await();
875 
876         final ActivityThread activityThread = activity.getActivityThread();
877         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
878             // Trigger callback with different ActivityWindowInfo
879             final Configuration config = new Configuration(activity.getResources()
880                     .getConfiguration());
881             config.seq++;
882             final Rect taskBounds = new Rect(0, 0, 1000, 2000);
883             final Rect taskFragmentBounds = new Rect(0, 0, 1000, 1000);
884             final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
885             activityWindowInfo.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
886             final ActivityConfigurationChangeItem activityConfigurationChangeItem =
887                     ActivityConfigurationChangeItem.obtain(
888                             activity.getActivityToken(), config, activityWindowInfo);
889             final ClientTransaction transaction = newTransaction(activity);
890             transaction.addTransactionItem(activityConfigurationChangeItem);
891 
892             clearInvocations(mActivityWindowInfoListener);
893             activityThread.executeTransaction(transaction);
894 
895             // Trigger callback with a different ActivityWindowInfo
896             verify(mActivityWindowInfoListener).accept(activity.getActivityToken(),
897                     activityWindowInfo);
898 
899             // Try callback with the same ActivityWindowInfo
900             final ActivityWindowInfo activityWindowInfo2 =
901                     new ActivityWindowInfo(activityWindowInfo);
902             config.seq++;
903             final ActivityConfigurationChangeItem activityConfigurationChangeItem2 =
904                     ActivityConfigurationChangeItem.obtain(
905                             activity.getActivityToken(), config, activityWindowInfo2);
906             final ClientTransaction transaction2 = newTransaction(activity);
907             transaction2.addTransactionItem(activityConfigurationChangeItem2);
908 
909             clearInvocations(mActivityWindowInfoListener);
910             activityThread.executeTransaction(transaction);
911 
912             // The same ActivityWindowInfo won't trigger duplicated callback.
913             verify(mActivityWindowInfoListener, never()).accept(any(), any());
914         });
915     }
916 
917     /**
918      * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
919      * Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the
920      * activity for the given sequence number.
921      * <p>
922      * It uses orientation to push the configuration and it tries a different orientation if the
923      * first attempt doesn't make through, to rule out the possibility that the previous
924      * configuration already has the same orientation.
925      *
926      * @param activity the test target activity
927      * @param seq the specified sequence number
928      * @return the sequence number this method tried with the last time, so that the caller can use
929      * the next sequence number for next configuration update.
930      */
applyConfigurationChange(TestActivity activity, int seq)931     private int applyConfigurationChange(TestActivity activity, int seq) {
932         final ActivityThread activityThread = activity.getActivityThread();
933         final ActivityClientRecord r = getActivityClientRecord(activity);
934 
935         final int numOfConfig = activity.mNumOfConfigChanges;
936         Configuration config = new Configuration();
937         config.orientation = ORIENTATION_PORTRAIT;
938         config.seq = seq;
939         activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY,
940                 new ActivityWindowInfo());
941 
942         if (activity.mNumOfConfigChanges > numOfConfig) {
943             return config.seq;
944         }
945 
946         config = new Configuration();
947         config.orientation = ORIENTATION_LANDSCAPE;
948         config.seq = seq + 1;
949         activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY,
950                 new ActivityWindowInfo());
951 
952         return config.seq;
953     }
954 
createVirtualDisplay(Context context, int w, int h)955     private Display createVirtualDisplay(Context context, int w, int h) {
956         final DisplayManager dm = context.getSystemService(DisplayManager.class);
957         final VirtualDisplay virtualDisplay = dm.createVirtualDisplay("virtual-display", w, h,
958                 200 /* densityDpi */, null /* surface */, 0 /* flags */);
959         if (mCreatedVirtualDisplays == null) {
960             mCreatedVirtualDisplays = new ArrayList<>();
961         }
962         mCreatedVirtualDisplays.add(virtualDisplay);
963         return virtualDisplay.getDisplay();
964     }
965 
getActivityClientRecord(Activity activity)966     private static ActivityClientRecord getActivityClientRecord(Activity activity) {
967         final ActivityThread thread = activity.getActivityThread();
968         final IBinder token = activity.getActivityToken();
969         return thread.getActivityClient(token);
970     }
971 
972     @NonNull
newRelaunchResumeTransaction(@onNull Activity activity)973     private static ClientTransaction newRelaunchResumeTransaction(@NonNull Activity activity) {
974         final Configuration currentConfig = activity.getResources().getConfiguration();
975         final ActivityClientRecord record = getActivityClientRecord(activity);
976         final ActivityWindowInfo activityWindowInfo;
977         if (record == null) {
978             Log.d(TAG, "The ActivityClientRecord of r=" + activity + " is not created yet. "
979                     + "Likely because this call doesn't wait until activity launch.");
980             activityWindowInfo = new ActivityWindowInfo();
981         } else {
982             activityWindowInfo = record.getActivityWindowInfo();
983         }
984         final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(
985                 activity.getActivityToken(), null, null, 0,
986                 new MergedConfiguration(currentConfig, currentConfig),
987                 false /* preserveWindow */, activityWindowInfo);
988         final ResumeActivityItem resumeStateRequest =
989                 ResumeActivityItem.obtain(activity.getActivityToken(), true /* isForward */,
990                         false /* shouldSendCompatFakeFocus*/);
991 
992         final ClientTransaction transaction = newTransaction(activity);
993         transaction.addTransactionItem(callbackItem);
994         transaction.addTransactionItem(resumeStateRequest);
995 
996         return transaction;
997     }
998 
999     @NonNull
newResumeTransaction(@onNull Activity activity)1000     private static ClientTransaction newResumeTransaction(@NonNull Activity activity) {
1001         final ResumeActivityItem resumeStateRequest =
1002                 ResumeActivityItem.obtain(activity.getActivityToken(), true /* isForward */,
1003                         false /* shouldSendCompatFakeFocus */);
1004 
1005         final ClientTransaction transaction = newTransaction(activity);
1006         transaction.addTransactionItem(resumeStateRequest);
1007 
1008         return transaction;
1009     }
1010 
1011     @NonNull
newStopTransaction(@onNull Activity activity)1012     private static ClientTransaction newStopTransaction(@NonNull Activity activity) {
1013         final StopActivityItem stopStateRequest = StopActivityItem.obtain(
1014                 activity.getActivityToken());
1015 
1016         final ClientTransaction transaction = newTransaction(activity);
1017         transaction.addTransactionItem(stopStateRequest);
1018 
1019         return transaction;
1020     }
1021 
1022     @NonNull
newActivityConfigTransaction(@onNull Activity activity, @NonNull Configuration config)1023     private static ClientTransaction newActivityConfigTransaction(@NonNull Activity activity,
1024             @NonNull Configuration config) {
1025         final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(
1026                 activity.getActivityToken(), config, new ActivityWindowInfo());
1027 
1028         final ClientTransaction transaction = newTransaction(activity);
1029         transaction.addTransactionItem(item);
1030 
1031         return transaction;
1032     }
1033 
1034     @NonNull
newNewIntentTransaction(@onNull Activity activity, @NonNull List<ReferrerIntent> intents, boolean resume)1035     private static ClientTransaction newNewIntentTransaction(@NonNull Activity activity,
1036             @NonNull List<ReferrerIntent> intents, boolean resume) {
1037         final NewIntentItem item = NewIntentItem.obtain(activity.getActivityToken(), intents,
1038                 resume);
1039 
1040         final ClientTransaction transaction = newTransaction(activity);
1041         transaction.addTransactionItem(item);
1042 
1043         return transaction;
1044     }
1045 
1046     @NonNull
newTransaction(@onNull Activity activity)1047     private static ClientTransaction newTransaction(@NonNull Activity activity) {
1048         return newTransaction(activity.getActivityThread());
1049     }
1050 
1051     @NonNull
newTransaction(@onNull ActivityThread activityThread)1052     private static ClientTransaction newTransaction(@NonNull ActivityThread activityThread) {
1053         return ClientTransaction.obtain(activityThread.getApplicationThread());
1054     }
1055 
1056     // Test activity
1057     public static class TestActivity extends Activity {
1058         static final String PIP_REQUESTED_OVERRIDE_ENTER = "pip_requested_override_enter";
1059         static final String PIP_REQUESTED_OVERRIDE_SKIP = "pip_requested_override_skip";
1060 
1061         int mNumOfConfigChanges;
1062         final Configuration mConfig = new Configuration();
1063 
1064         private boolean mPipRequested;
1065         private boolean mPipEntered;
1066         private boolean mPipEnterSkipped;
1067 
1068         /**
1069          * A latch used to notify tests that we're about to wait for configuration latch. This
1070          * is used to notify test code that preExecute phase for activity configuration change
1071          * transaction has passed.
1072          */
1073         volatile CountDownLatch mTestLatch;
1074         /**
1075          * If not {@code null} {@link #onConfigurationChanged(Configuration)} won't return until the
1076          * latch reaches 0.
1077          */
1078         volatile CountDownLatch mConfigLatch;
1079         /**
1080          * A latch used to notify tests that we're about to wait for the
1081          * onPictureInPictureUiStateChanged callback.
1082          */
1083         volatile CountDownLatch mPipUiStateLatch;
1084 
1085         @Override
onCreate(Bundle savedInstanceState)1086         protected void onCreate(Bundle savedInstanceState) {
1087             super.onCreate(savedInstanceState);
1088             getWindow().getDecorView().setKeepScreenOn(true);
1089             setShowWhenLocked(true);
1090             setTurnScreenOn(true);
1091         }
1092 
1093         @Override
onConfigurationChanged(Configuration config)1094         public void onConfigurationChanged(Configuration config) {
1095             super.onConfigurationChanged(config);
1096             mConfig.setTo(config);
1097             ++mNumOfConfigChanges;
1098 
1099             final CountDownLatch configLatch = mConfigLatch;
1100             if (configLatch != null) {
1101                 if (mTestLatch != null) {
1102                     mTestLatch.countDown();
1103                 }
1104                 try {
1105                     configLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
1106                 } catch (InterruptedException e) {
1107                     throw new IllegalStateException(e);
1108                 }
1109             }
1110         }
1111 
1112         @Override
onPictureInPictureRequested()1113         public boolean onPictureInPictureRequested() {
1114             mPipRequested = true;
1115             if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_ENTER, false)) {
1116                 enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
1117                 mPipEntered = true;
1118                 // Await for onPictureInPictureUiStateChanged callback if applicable
1119                 if (mPipUiStateLatch != null) {
1120                     try {
1121                         mPipUiStateLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
1122                     } catch (InterruptedException e) {
1123                         throw new IllegalStateException(e);
1124                     }
1125                 }
1126                 return true;
1127             } else if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_SKIP, false)) {
1128                 mPipEnterSkipped = true;
1129                 return false;
1130             }
1131             return super.onPictureInPictureRequested();
1132         }
1133 
1134         @Override
onPictureInPictureUiStateChanged(PictureInPictureUiState pipState)1135         public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) {
1136             if (mPipUiStateLatch != null && pipState.isTransitioningToPip()) {
1137                 mPipUiStateLatch.countDown();
1138             }
1139         }
1140 
pipRequested()1141         boolean pipRequested() {
1142             return mPipRequested;
1143         }
1144 
enteredPip()1145         boolean enteredPip() {
1146             return mPipEntered;
1147         }
1148 
enterPipSkipped()1149         boolean enterPipSkipped() {
1150             return mPipEnterSkipped;
1151         }
1152     }
1153 
1154     public static class ActivityWindowInfoListener implements
1155             BiConsumer<IBinder, ActivityWindowInfo> {
1156 
1157         CountDownLatch mCallbackLatch = new CountDownLatch(1);
1158 
1159         @Override
accept(@onNull IBinder activityToken, @NonNull ActivityWindowInfo activityWindowInfo)1160         public void accept(@NonNull IBinder activityToken,
1161                 @NonNull ActivityWindowInfo activityWindowInfo) {
1162             mCallbackLatch.countDown();
1163         }
1164 
1165         /**
1166          * When the test is expecting to receive a callback, waits until the callback is triggered.
1167          */
await()1168         void await() {
1169             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
1170             try {
1171                 mCallbackLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
1172             } catch (InterruptedException e) {
1173                 throw new RuntimeException(e);
1174             }
1175         }
1176     }
1177 }
1178