1 /*
2  * Copyright (C) 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 com.android.server.wm;
18 
19 import static android.os.Process.NOBODY_UID;
20 import static android.view.Display.DEFAULT_DISPLAY;
21 import static android.view.Surface.ROTATION_0;
22 import static android.view.Surface.ROTATION_90;
23 
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
27 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
29 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
30 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
32 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
33 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
34 import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
35 import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
36 import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
37 import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
38 import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
39 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING;
40 import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_INVISIBLE;
41 import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
42 import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
43 
44 import static com.google.common.truth.Truth.assertThat;
45 
46 import static org.junit.Assert.assertEquals;
47 import static org.junit.Assert.assertFalse;
48 import static org.junit.Assert.assertNotEquals;
49 import static org.junit.Assert.assertNotNull;
50 import static org.junit.Assert.assertNull;
51 import static org.junit.Assert.assertTrue;
52 import static org.mockito.ArgumentMatchers.anyString;
53 
54 import android.app.ActivityManager;
55 import android.app.ActivityManagerInternal;
56 import android.app.ActivityOptions;
57 import android.app.servertransaction.ActivityConfigurationChangeItem;
58 import android.app.servertransaction.ClientTransaction;
59 import android.app.servertransaction.PauseActivityItem;
60 import android.content.ComponentName;
61 import android.content.pm.ActivityInfo;
62 import android.content.res.Configuration;
63 import android.content.res.Resources;
64 import android.graphics.Rect;
65 import android.platform.test.annotations.Presubmit;
66 import android.util.MergedConfiguration;
67 import android.util.MutableBoolean;
68 import android.view.IRemoteAnimationFinishedCallback;
69 import android.view.IRemoteAnimationRunner.Stub;
70 import android.view.RemoteAnimationAdapter;
71 import android.view.RemoteAnimationTarget;
72 
73 import androidx.test.filters.MediumTest;
74 
75 import com.android.internal.R;
76 import com.android.server.wm.utils.WmDisplayCutout;
77 
78 import org.junit.Before;
79 import org.junit.Test;
80 import org.mockito.invocation.InvocationOnMock;
81 
82 import java.util.concurrent.TimeUnit;
83 
84 /**
85  * Tests for the {@link ActivityRecord} class.
86  *
87  * Build/Install/Run:
88  *  atest WmTests:ActivityRecordTests
89  */
90 @MediumTest
91 @Presubmit
92 public class ActivityRecordTests extends ActivityTestsBase {
93     private TestActivityStack mStack;
94     private TaskRecord mTask;
95     private ActivityRecord mActivity;
96 
97     @Before
setUp()98     public void setUp() throws Exception {
99         mStack = (TestActivityStack) new StackBuilder(mRootActivityContainer).build();
100         mTask = mStack.getChildAt(0);
101         mActivity = mTask.getTopActivity();
102 
103         doReturn(false).when(mService).isBooting();
104         doReturn(true).when(mService).isBooted();
105     }
106 
107     @Test
testStackCleanupOnClearingTask()108     public void testStackCleanupOnClearingTask() {
109         mActivity.setTask(null);
110         assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1);
111     }
112 
113     @Test
testStackCleanupOnActivityRemoval()114     public void testStackCleanupOnActivityRemoval() {
115         mTask.removeActivity(mActivity);
116         assertEquals(mStack.onActivityRemovedFromStackInvocationCount(),  1);
117     }
118 
119     @Test
testStackCleanupOnTaskRemoval()120     public void testStackCleanupOnTaskRemoval() {
121         mStack.removeTask(mTask, null /*reason*/, REMOVE_TASK_MODE_MOVING);
122         // Stack should be gone on task removal.
123         assertNull(mService.mRootActivityContainer.getStack(mStack.mStackId));
124     }
125 
126     @Test
testNoCleanupMovingActivityInSameStack()127     public void testNoCleanupMovingActivityInSameStack() {
128         final TaskRecord newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack)
129                 .build();
130         mActivity.reparent(newTask, 0, null /*reason*/);
131         assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 0);
132     }
133 
134     @Test
testPausingWhenVisibleFromStopped()135     public void testPausingWhenVisibleFromStopped() throws Exception {
136         final MutableBoolean pauseFound = new MutableBoolean(false);
137         doAnswer((InvocationOnMock invocationOnMock) -> {
138             final ClientTransaction transaction = invocationOnMock.getArgument(0);
139             if (transaction.getLifecycleStateRequest() instanceof PauseActivityItem) {
140                 pauseFound.value = true;
141             }
142             return null;
143         }).when(mActivity.app.getThread()).scheduleTransaction(any());
144 
145         mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
146 
147         // The activity is in the focused stack so it should be resumed.
148         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
149         assertTrue(mActivity.isState(RESUMED));
150         assertFalse(pauseFound.value);
151 
152         // Make the activity non focusable
153         mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
154         doReturn(false).when(mActivity).isFocusable();
155 
156         // If the activity is not focusable, it should move to paused.
157         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
158         assertTrue(mActivity.isState(PAUSING));
159         assertTrue(pauseFound.value);
160 
161         // Make sure that the state does not change for current non-stopping states.
162         mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped");
163         doReturn(true).when(mActivity).isFocusable();
164 
165         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
166 
167         assertTrue(mActivity.isState(INITIALIZING));
168 
169         // Make sure the state does not change if we are not the current top activity.
170         mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped behind");
171 
172         final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
173         mStack.mTranslucentActivityWaiting = topActivity;
174         mActivity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
175         assertTrue(mActivity.isState(PAUSED));
176     }
177 
ensureActivityConfiguration()178     private void ensureActivityConfiguration() {
179         mActivity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
180     }
181 
182     @Test
testCanBeLaunchedOnDisplay()183     public void testCanBeLaunchedOnDisplay() {
184         mService.mSupportsMultiWindow = true;
185         final ActivityRecord activity = new ActivityBuilder(mService).build();
186 
187         // An activity can be launched on default display.
188         assertTrue(activity.canBeLaunchedOnDisplay(DEFAULT_DISPLAY));
189         // An activity cannot be launched on a non-existent display.
190         assertFalse(activity.canBeLaunchedOnDisplay(DEFAULT_DISPLAY + 1));
191     }
192 
193     @Test
testRestartProcessIfVisible()194     public void testRestartProcessIfVisible() {
195         doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
196         mActivity.visible = true;
197         mActivity.haveState = false;
198         mActivity.setState(ActivityStack.ActivityState.RESUMED, "testRestart");
199         prepareFixedAspectRatioUnresizableActivity();
200 
201         final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
202         setupDisplayAndParentSize(600, 1200);
203         // The visible activity should recompute configuration according to the last parent bounds.
204         mService.restartActivityProcessIfVisible(mActivity.appToken);
205 
206         assertEquals(ActivityStack.ActivityState.RESTARTING_PROCESS, mActivity.getState());
207         assertNotEquals(originalOverrideBounds, mActivity.getBounds());
208     }
209 
210     @Test
testsApplyOptionsLocked()211     public void testsApplyOptionsLocked() {
212         ActivityOptions activityOptions = ActivityOptions.makeBasic();
213 
214         // Set and apply options for ActivityRecord. Pending options should be cleared
215         mActivity.updateOptionsLocked(activityOptions);
216         mActivity.applyOptionsLocked();
217         assertNull(mActivity.pendingOptions);
218 
219         // Set options for two ActivityRecords in same Task. Apply one ActivityRecord options.
220         // Pending options should be cleared for both ActivityRecords
221         ActivityRecord activity2 = new ActivityBuilder(mService).setTask(mTask).build();
222         activity2.updateOptionsLocked(activityOptions);
223         mActivity.updateOptionsLocked(activityOptions);
224         mActivity.applyOptionsLocked();
225         assertNull(mActivity.pendingOptions);
226         assertNull(activity2.pendingOptions);
227 
228         // Set options for two ActivityRecords in separate Tasks. Apply one ActivityRecord options.
229         // Pending options should be cleared for only ActivityRecord that was applied
230         TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
231         activity2 = new ActivityBuilder(mService).setTask(task2).build();
232         activity2.updateOptionsLocked(activityOptions);
233         mActivity.updateOptionsLocked(activityOptions);
234         mActivity.applyOptionsLocked();
235         assertNull(mActivity.pendingOptions);
236         assertNotNull(activity2.pendingOptions);
237     }
238 
239     @Test
testNewOverrideConfigurationIncrementsSeq()240     public void testNewOverrideConfigurationIncrementsSeq() {
241         final Configuration newConfig = new Configuration();
242 
243         final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
244         mActivity.onRequestedOverrideConfigurationChanged(newConfig);
245         assertEquals(prevSeq + 1, mActivity.getMergedOverrideConfiguration().seq);
246     }
247 
248     @Test
testNewParentConfigurationIncrementsSeq()249     public void testNewParentConfigurationIncrementsSeq() {
250         final Configuration newConfig = new Configuration(
251                 mTask.getRequestedOverrideConfiguration());
252         newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
253                 ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
254 
255         final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
256         mTask.onRequestedOverrideConfigurationChanged(newConfig);
257         assertEquals(prevSeq + 1, mActivity.getMergedOverrideConfiguration().seq);
258     }
259 
260     @Test
testNotifiesSeqIncrementToAppToken()261     public void testNotifiesSeqIncrementToAppToken() {
262         final Configuration appWindowTokenRequestedOrientation = mock(Configuration.class);
263         mActivity.mAppWindowToken = mock(AppWindowToken.class);
264         doReturn(appWindowTokenRequestedOrientation).when(mActivity.mAppWindowToken)
265                 .getRequestedOverrideConfiguration();
266 
267         final Configuration newConfig = new Configuration();
268         newConfig.orientation = Configuration.ORIENTATION_PORTRAIT;
269 
270         final int prevSeq = mActivity.getMergedOverrideConfiguration().seq;
271         mActivity.onRequestedOverrideConfigurationChanged(newConfig);
272         assertEquals(prevSeq + 1, appWindowTokenRequestedOrientation.seq);
273         verify(mActivity.mAppWindowToken).onMergedOverrideConfigurationChanged();
274     }
275 
276     @Test
testSetsRelaunchReason_NotDragResizing()277     public void testSetsRelaunchReason_NotDragResizing() {
278         mActivity.setState(ActivityStack.ActivityState.RESUMED, "Testing");
279 
280         mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
281         mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
282                 mActivity.getConfiguration()));
283 
284         mActivity.info.configChanges &= ~ActivityInfo.CONFIG_ORIENTATION;
285         final Configuration newConfig = new Configuration(mTask.getConfiguration());
286         newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
287                 ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
288         mTask.onRequestedOverrideConfigurationChanged(newConfig);
289 
290         mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
291 
292         ensureActivityConfiguration();
293 
294         assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE,
295                 mActivity.mRelaunchReason);
296     }
297 
298     @Test
testSetsRelaunchReason_DragResizing()299     public void testSetsRelaunchReason_DragResizing() {
300         mActivity.setState(ActivityStack.ActivityState.RESUMED, "Testing");
301 
302         mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
303         mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
304                 mActivity.getConfiguration()));
305 
306         mActivity.info.configChanges &= ~ActivityInfo.CONFIG_ORIENTATION;
307         final Configuration newConfig = new Configuration(mTask.getConfiguration());
308         newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
309                 ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
310         mTask.onRequestedOverrideConfigurationChanged(newConfig);
311 
312         doReturn(true).when(mTask.getTask()).isDragResizing();
313 
314         mActivity.mRelaunchReason = ActivityTaskManagerService.RELAUNCH_REASON_NONE;
315 
316         ensureActivityConfiguration();
317 
318         assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE,
319                 mActivity.mRelaunchReason);
320     }
321 
322     @Test
testSetsRelaunchReason_NonResizeConfigChanges()323     public void testSetsRelaunchReason_NonResizeConfigChanges() {
324         mActivity.setState(ActivityStack.ActivityState.RESUMED, "Testing");
325 
326         mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
327         mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
328                 mActivity.getConfiguration()));
329 
330         mActivity.info.configChanges &= ~ActivityInfo.CONFIG_FONT_SCALE;
331         final Configuration newConfig = new Configuration(mTask.getConfiguration());
332         newConfig.fontScale = 5;
333         mTask.onRequestedOverrideConfigurationChanged(newConfig);
334 
335         mActivity.mRelaunchReason =
336                 ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
337 
338         ensureActivityConfiguration();
339 
340         assertEquals(ActivityTaskManagerService.RELAUNCH_REASON_NONE,
341                 mActivity.mRelaunchReason);
342     }
343 
344     @Test
testSetRequestedOrientationUpdatesConfiguration()345     public void testSetRequestedOrientationUpdatesConfiguration() throws Exception {
346         mActivity.setState(ActivityStack.ActivityState.RESUMED, "Testing");
347 
348         mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
349         mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
350                 mActivity.getConfiguration()));
351 
352         mActivity.info.configChanges |= ActivityInfo.CONFIG_ORIENTATION;
353         final Configuration newConfig = new Configuration(mActivity.getConfiguration());
354         newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
355                 ? Configuration.ORIENTATION_LANDSCAPE
356                 : Configuration.ORIENTATION_PORTRAIT;
357 
358         // Mimic the behavior that display doesn't handle app's requested orientation.
359         doAnswer(invocation -> {
360             mTask.onConfigurationChanged(newConfig);
361             return null;
362         }).when(mActivity.mAppWindowToken).setOrientation(anyInt(), any(), any());
363 
364         final int requestedOrientation;
365         switch (newConfig.orientation) {
366             case Configuration.ORIENTATION_LANDSCAPE:
367                 requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
368                 break;
369             case Configuration.ORIENTATION_PORTRAIT:
370                 requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
371                 break;
372             default:
373                 throw new IllegalStateException("Orientation in new config should be either"
374                         + "landscape or portrait.");
375         }
376         mActivity.setRequestedOrientation(requestedOrientation);
377 
378         final ActivityConfigurationChangeItem expected =
379                 ActivityConfigurationChangeItem.obtain(newConfig);
380         verify(mService.getLifecycleManager()).scheduleTransaction(eq(mActivity.app.getThread()),
381                 eq(mActivity.appToken), eq(expected));
382     }
383 
384     @Test
testShouldMakeActive_deferredResume()385     public void testShouldMakeActive_deferredResume() {
386         mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
387 
388         mSupervisor.beginDeferResume();
389         assertEquals(false, mActivity.shouldMakeActive(null /* activeActivity */));
390 
391         mSupervisor.endDeferResume();
392         assertEquals(true, mActivity.shouldMakeActive(null /* activeActivity */));
393     }
394 
395     @Test
testShouldResume_stackVisibility()396     public void testShouldResume_stackVisibility() {
397         mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
398         spyOn(mStack);
399 
400         doReturn(STACK_VISIBILITY_VISIBLE).when(mStack).getVisibility(null);
401         assertEquals(true, mActivity.shouldResumeActivity(null /* activeActivity */));
402 
403         doReturn(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT).when(mStack).getVisibility(null);
404         assertEquals(false, mActivity.shouldResumeActivity(null /* activeActivity */));
405 
406         doReturn(STACK_VISIBILITY_INVISIBLE).when(mStack).getVisibility(null);
407         assertEquals(false, mActivity.shouldResumeActivity(null /* activeActivity */));
408     }
409 
410     @Test
testPushConfigurationWhenLaunchTaskBehind()411     public void testPushConfigurationWhenLaunchTaskBehind() throws Exception {
412         mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
413 
414         final TestActivityStack stack = (TestActivityStack) new StackBuilder(mRootActivityContainer)
415                 .build();
416         try {
417             stack.setIsTranslucent(false);
418             assertFalse(mStack.shouldBeVisible(null /* starting */));
419 
420             mTask.onRequestedOverrideConfigurationChanged(mTask.getConfiguration());
421             mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
422                     mActivity.getConfiguration()));
423 
424             mActivity.mLaunchTaskBehind = true;
425             mActivity.info.configChanges |= ActivityInfo.CONFIG_ORIENTATION;
426             final Configuration newConfig = new Configuration(mActivity.getConfiguration());
427             newConfig.orientation = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
428                     ? Configuration.ORIENTATION_LANDSCAPE
429                     : Configuration.ORIENTATION_PORTRAIT;
430 
431             mTask.onConfigurationChanged(newConfig);
432 
433             mActivity.ensureActivityConfiguration(0 /* globalChanges */,
434                     false /* preserveWindow */, true /* ignoreStopState */);
435 
436             final ActivityConfigurationChangeItem expected =
437                     ActivityConfigurationChangeItem.obtain(newConfig);
438             verify(mService.getLifecycleManager()).scheduleTransaction(
439                     eq(mActivity.app.getThread()), eq(mActivity.appToken), eq(expected));
440         } finally {
441             stack.getDisplay().removeChild(stack);
442         }
443     }
444 
445     @Test
testShouldPauseWhenMakeClientVisible()446     public void testShouldPauseWhenMakeClientVisible() {
447         ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
448         topActivity.changeWindowTranslucency(false);
449         mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
450         mActivity.makeClientVisible();
451         assertEquals(PAUSED, mActivity.getState());
452     }
453 
454     @Test
testSizeCompatMode_FixedAspectRatioBoundsWithDecor()455     public void testSizeCompatMode_FixedAspectRatioBoundsWithDecor() {
456         setupDisplayContentForCompatDisplayInsets();
457         final int decorHeight = 200; // e.g. The device has cutout.
458         final DisplayPolicy policy = setupDisplayAndParentSize(600, 800).getDisplayPolicy();
459         doAnswer(invocationOnMock -> {
460             final int rotation = invocationOnMock.<Integer>getArgument(0);
461             final Rect insets = invocationOnMock.<Rect>getArgument(4);
462             if (rotation == ROTATION_0) {
463                 insets.top = decorHeight;
464             } else if (rotation == ROTATION_90) {
465                 insets.left = decorHeight;
466             }
467             return null;
468         }).when(policy).getNonDecorInsetsLw(anyInt() /* rotation */, anyInt() /* width */,
469                 anyInt() /* height */, any() /* displayCutout */, any() /* outInsets */);
470 
471         doReturn(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
472                 .when(mActivity.mAppWindowToken).getOrientationIgnoreVisibility();
473         mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
474         mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1;
475         ensureActivityConfiguration();
476         // The parent configuration doesn't change since the first resolved configuration, so the
477         // activity shouldn't be in the size compatibility mode.
478         assertFalse(mActivity.inSizeCompatMode());
479 
480         final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds();
481         // Ensure the app bounds keep the declared aspect ratio.
482         assertEquals(appBounds.width(), appBounds.height());
483         // The decor height should be a part of the effective bounds.
484         assertEquals(mActivity.getBounds().height(), appBounds.height() + decorHeight);
485 
486         mTask.getConfiguration().windowConfiguration.setRotation(ROTATION_90);
487         mActivity.onConfigurationChanged(mTask.getConfiguration());
488         // After changing orientation, the aspect ratio should be the same.
489         assertEquals(appBounds.width(), appBounds.height());
490         // The decor height will be included in width.
491         assertEquals(mActivity.getBounds().width(), appBounds.width() + decorHeight);
492     }
493 
494     @Test
testSizeCompatMode_FixedScreenConfigurationWhenMovingToDisplay()495     public void testSizeCompatMode_FixedScreenConfigurationWhenMovingToDisplay() {
496         // Initialize different bounds on a new display.
497         final ActivityDisplay newDisplay = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
498         newDisplay.getWindowConfiguration().setAppBounds(new Rect(0, 0, 1000, 2000));
499         newDisplay.getConfiguration().densityDpi = 300;
500 
501         mTask.getConfiguration().densityDpi = 200;
502         prepareFixedAspectRatioUnresizableActivity();
503 
504         final Rect originalBounds = new Rect(mActivity.getBounds());
505         final int originalDpi = mActivity.getConfiguration().densityDpi;
506 
507         // Move the non-resizable activity to the new display.
508         mStack.reparent(newDisplay, true /* onTop */, false /* displayRemoved */);
509         ensureActivityConfiguration();
510 
511         assertEquals(originalBounds, mActivity.getBounds());
512         assertEquals(originalDpi, mActivity.getConfiguration().densityDpi);
513         assertTrue(mActivity.inSizeCompatMode());
514     }
515 
516     @Test
testSizeCompatMode_FixedScreenBoundsWhenDisplaySizeChanged()517     public void testSizeCompatMode_FixedScreenBoundsWhenDisplaySizeChanged() {
518         setupDisplayContentForCompatDisplayInsets();
519         when(mActivity.mAppWindowToken.getOrientationIgnoreVisibility()).thenReturn(
520                 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
521         mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds());
522         mTask.getConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT;
523         mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
524         mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
525         ensureActivityConfiguration();
526         final Rect originalBounds = new Rect(mActivity.getBounds());
527 
528         // Change the size of current display.
529         setupDisplayAndParentSize(1000, 2000);
530         ensureActivityConfiguration();
531 
532         assertEquals(originalBounds, mActivity.getBounds());
533         assertTrue(mActivity.inSizeCompatMode());
534     }
535 
536     @Test
testSizeCompatMode_FixedScreenLayoutSizeBits()537     public void testSizeCompatMode_FixedScreenLayoutSizeBits() {
538         final int fixedScreenLayout = Configuration.SCREENLAYOUT_LONG_NO
539                 | Configuration.SCREENLAYOUT_SIZE_NORMAL;
540         mTask.getConfiguration().screenLayout = fixedScreenLayout
541                 | Configuration.SCREENLAYOUT_LAYOUTDIR_LTR;
542         prepareFixedAspectRatioUnresizableActivity();
543 
544         // The initial configuration should inherit from parent.
545         assertEquals(mTask.getConfiguration().screenLayout,
546                 mActivity.getConfiguration().screenLayout);
547 
548         mTask.getConfiguration().screenLayout = Configuration.SCREENLAYOUT_LAYOUTDIR_RTL
549                 | Configuration.SCREENLAYOUT_LONG_YES | Configuration.SCREENLAYOUT_SIZE_LARGE;
550         mActivity.onConfigurationChanged(mTask.getConfiguration());
551 
552         // The size and aspect ratio bits don't change, but the layout direction should be updated.
553         assertEquals(fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_RTL,
554                 mActivity.getConfiguration().screenLayout);
555     }
556 
557     @Test
testSizeCompatMode_ResetNonVisibleActivity()558     public void testSizeCompatMode_ResetNonVisibleActivity() {
559         final ActivityDisplay display = mStack.getDisplay();
560         spyOn(display);
561 
562         prepareFixedAspectRatioUnresizableActivity();
563         mActivity.setState(STOPPED, "testSizeCompatMode");
564         mActivity.visible = false;
565         mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
566         // Make the parent bounds to be different so the activity is in size compatibility mode.
567         mTask.getWindowConfiguration().setAppBounds(new Rect(0, 0, 600, 1200));
568 
569         // Simulate the display changes orientation.
570         doReturn(ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION
571                 | ActivityInfo.CONFIG_WINDOW_CONFIGURATION)
572                         .when(display).getLastOverrideConfigurationChanges();
573         mActivity.onConfigurationChanged(mTask.getConfiguration());
574         // The override configuration should not change so it is still in size compatibility mode.
575         assertTrue(mActivity.inSizeCompatMode());
576 
577         // Simulate the display changes density.
578         doReturn(ActivityInfo.CONFIG_DENSITY).when(display).getLastOverrideConfigurationChanges();
579         mService.mAmInternal = mock(ActivityManagerInternal.class);
580         mActivity.onConfigurationChanged(mTask.getConfiguration());
581         // The override configuration should be reset and the activity's process will be killed.
582         assertFalse(mActivity.inSizeCompatMode());
583         verify(mActivity).restartProcessIfVisible();
584         mService.mH.runWithScissors(() -> { }, TimeUnit.SECONDS.toMillis(3));
585         verify(mService.mAmInternal).killProcess(
586                 eq(mActivity.app.mName), eq(mActivity.app.mUid), anyString());
587     }
588 
589     @Test
testTakeOptions()590     public void testTakeOptions() {
591         ActivityOptions opts = ActivityOptions.makeRemoteAnimation(
592                 new RemoteAnimationAdapter(new Stub() {
593 
594                     @Override
595                     public void onAnimationStart(RemoteAnimationTarget[] apps,
596                             IRemoteAnimationFinishedCallback finishedCallback) {
597 
598                     }
599 
600                     @Override
601                     public void onAnimationCancelled() {
602 
603                     }
604                 }, 0, 0));
605         mActivity.updateOptionsLocked(opts);
606         assertNotNull(mActivity.takeOptionsLocked(true /* fromClient */));
607         assertNotNull(mActivity.pendingOptions);
608 
609         mActivity.updateOptionsLocked(ActivityOptions.makeBasic());
610         assertNotNull(mActivity.takeOptionsLocked(false /* fromClient */));
611         assertNull(mActivity.pendingOptions);
612     }
613 
614     @Test
testCanLaunchHomeActivityFromChooser()615     public void testCanLaunchHomeActivityFromChooser() {
616         ComponentName chooserComponent = ComponentName.unflattenFromString(
617                 Resources.getSystem().getString(R.string.config_chooserActivity));
618         ActivityRecord chooserActivity = new ActivityBuilder(mService).setComponent(
619                 chooserComponent).build();
620         assertThat(mActivity.canLaunchHomeActivity(NOBODY_UID, chooserActivity)).isTrue();
621     }
622 
623     /** Setup {@link #mActivity} as a size-compat-mode-able activity without fixed orientation. */
prepareFixedAspectRatioUnresizableActivity()624     private void prepareFixedAspectRatioUnresizableActivity() {
625         setupDisplayContentForCompatDisplayInsets();
626         when(mActivity.mAppWindowToken.getOrientationIgnoreVisibility()).thenReturn(
627                 ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
628         mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
629         mActivity.info.maxAspectRatio = 1.5f;
630         ensureActivityConfiguration();
631     }
632 
setupDisplayContentForCompatDisplayInsets()633     private void setupDisplayContentForCompatDisplayInsets() {
634         final Rect displayBounds = mStack.getDisplay().getBounds();
635         final DisplayContent displayContent = setupDisplayAndParentSize(
636                 displayBounds.width(), displayBounds.height());
637         doReturn(mock(DisplayPolicy.class)).when(displayContent).getDisplayPolicy();
638         doReturn(mock(WmDisplayCutout.class)).when(displayContent)
639                 .calculateDisplayCutoutForRotation(anyInt());
640     }
641 
setupDisplayAndParentSize(int width, int height)642     private DisplayContent setupDisplayAndParentSize(int width, int height) {
643         // The DisplayContent is already a mocked object.
644         final DisplayContent displayContent = mStack.getDisplay().mDisplayContent;
645         displayContent.mBaseDisplayWidth = width;
646         displayContent.mBaseDisplayHeight = height;
647         mTask.getWindowConfiguration().setAppBounds(0, 0, width, height);
648         mTask.getWindowConfiguration().setRotation(ROTATION_0);
649         return displayContent;
650     }
651 }
652