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