Merge "Separate and add more SizeCompat tests; add Display Builder"
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e80c9b3..deb3246 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6171,8 +6171,7 @@
// The rest of the condition is that only one side is smaller than the parent, but it still
// needs to exclude the cases where the size is limited by the fixed aspect ratio.
if (info.maxAspectRatio > 0) {
- final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
- / Math.min(appWidth, appHeight);
+ final float aspectRatio = Math.max(appWidth, appHeight) / Math.min(appWidth, appHeight);
if (aspectRatio >= info.maxAspectRatio) {
// The current size has reached the max aspect ratio.
return false;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
index 9df7b45..864f946 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
@@ -38,12 +38,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.when;
-import android.app.TaskStackListener;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -51,10 +46,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.ArrayList;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
-
/**
* Tests for the {@link ActivityDisplay} class.
*
@@ -331,50 +322,4 @@
verify(mSupervisor).removeTaskByIdLocked(eq(task1.mTaskId), anyBoolean(), anyBoolean(),
any());
}
-
- /**
- * Ensures that {@link TaskStackListener} can receive callback about the activity in size
- * compatibility mode.
- */
- @Test
- public void testHandleActivitySizeCompatMode() throws Exception {
- final ActivityDisplay display = mRootActivityContainer.getDefaultDisplay();
- final ActivityRecord activity = createFullscreenStackWithSimpleActivityAt(
- display).topRunningActivityLocked();
- activity.setState(ActivityStack.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
- when(activity.getRequestedOrientation()).thenReturn(
- ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- activity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
- activity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
- activity.visible = true;
- activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
-
- final ArrayList<CompletableFuture<IBinder>> resultWrapper = new ArrayList<>();
- mService.getTaskChangeNotificationController().registerTaskStackListener(
- new TaskStackListener() {
- @Override
- public void onSizeCompatModeActivityChanged(int displayId,
- IBinder activityToken) {
- resultWrapper.get(0).complete(activityToken);
- }
- });
-
- resultWrapper.add(new CompletableFuture<>());
-
- // resize the display to exercise size-compat mode
- final DisplayContent displayContent = display.mDisplayContent;
- displayContent.mBaseDisplayHeight = (int) (0.8f * displayContent.mBaseDisplayHeight);
- Configuration c = new Configuration();
- displayContent.computeScreenConfiguration(c);
- display.onRequestedOverrideConfigurationChanged(c);
-
- assertEquals(activity.appToken, resultWrapper.get(0).get(2, TimeUnit.SECONDS));
-
- // Expect null token when switching to non-size-compat mode activity.
- activity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
- resultWrapper.set(0, new CompletableFuture<>());
- display.handleActivitySizeCompatModeIfNeeded(activity);
-
- assertNull(resultWrapper.get(0).get(2, TimeUnit.SECONDS));
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index c51a46a..ac1da7c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -18,24 +18,18 @@
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
-import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
@@ -61,18 +55,13 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.when;
-import android.app.ActivityManager;
-import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
-import android.app.WindowConfiguration;
import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.PauseActivityItem;
@@ -80,13 +69,11 @@
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Rect;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.platform.test.annotations.Presubmit;
import android.util.MergedConfiguration;
import android.util.MutableBoolean;
-import android.view.DisplayInfo;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner.Stub;
import android.view.RemoteAnimationAdapter;
@@ -219,23 +206,6 @@
}
@Test
- public void testRestartProcessIfVisible() {
- doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
- mActivity.visible = true;
- mActivity.setSavedState(null /* savedState */);
- mActivity.setState(ActivityStack.ActivityState.RESUMED, "testRestart");
- prepareFixedAspectRatioUnresizableActivity();
-
- final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
- setupDisplayAndParentSize(600, 1200);
- // The visible activity should recompute configuration according to the last parent bounds.
- mService.restartActivityProcessIfVisible(mActivity.appToken);
-
- assertEquals(ActivityStack.ActivityState.RESTARTING_PROCESS, mActivity.getState());
- assertNotEquals(originalOverrideBounds, mActivity.getBounds());
- }
-
- @Test
public void testsApplyOptionsLocked() {
ActivityOptions activityOptions = ActivityOptions.makeBasic();
@@ -484,214 +454,6 @@
}
@Test
- public void testSizeCompatMode_KeepBoundsWhenChangingFromFreeformToFullscreen() {
- setupDisplayContentForCompatDisplayInsets();
-
- // put display in freeform mode
- ActivityDisplay display = mActivity.getDisplay();
- final Configuration c = new Configuration(display.getRequestedOverrideConfiguration());
- c.windowConfiguration.setBounds(new Rect(0, 0, 2000, 1000));
- c.densityDpi = 300;
- c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
- display.onRequestedOverrideConfigurationChanged(c);
-
- // launch compat activity in freeform and store bounds
- when(mActivity.getRequestedOrientation()).thenReturn(
- ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- mTask.getRequestedOverrideConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT;
- mTask.setBounds(100, 100, 400, 600);
- mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
- mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
- mActivity.visible = true;
- ensureActivityConfiguration();
-
- final Rect bounds = new Rect(mActivity.getBounds());
- final int density = mActivity.getConfiguration().densityDpi;
-
- // change display configuration to fullscreen
- c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
- display.onRequestedOverrideConfigurationChanged(c);
-
- // check if dimensions stay the same
- assertTrue(mActivity.inSizeCompatMode());
- assertEquals(bounds.width(), mActivity.getBounds().width());
- assertEquals(bounds.height(), mActivity.getBounds().height());
- assertEquals(density, mActivity.getConfiguration().densityDpi);
- assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, mActivity.getWindowingMode());
- }
-
- @Test
- public void testSizeCompatMode_FixedAspectRatioBoundsWithDecor() {
- setupDisplayContentForCompatDisplayInsets();
- final int decorHeight = 200; // e.g. The device has cutout.
- final DisplayPolicy policy = setupDisplayAndParentSize(600, 800).getDisplayPolicy();
- spyOn(policy);
- doAnswer(invocationOnMock -> {
- final int rotation = invocationOnMock.<Integer>getArgument(0);
- final Rect insets = invocationOnMock.<Rect>getArgument(4);
- if (rotation == ROTATION_0) {
- insets.top = decorHeight;
- } else if (rotation == ROTATION_90) {
- insets.left = decorHeight;
- }
- return null;
- }).when(policy).getNonDecorInsetsLw(anyInt() /* rotation */, anyInt() /* width */,
- anyInt() /* height */, any() /* displayCutout */, any() /* outInsets */);
- // set appBounds to incorporate decor
- final Configuration c =
- new Configuration(mStack.getDisplay().getRequestedOverrideConfiguration());
- c.windowConfiguration.getAppBounds().top = decorHeight;
- mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);
-
- doReturn(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
- .when(mActivity).getRequestedOrientation();
- mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
- mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1;
- mActivity.visible = true;
- ensureActivityConfiguration();
- // The parent configuration doesn't change since the first resolved configuration, so the
- // activity shouldn't be in the size compatibility mode.
- assertFalse(mActivity.inSizeCompatMode());
-
- final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds();
- // Ensure the app bounds keep the declared aspect ratio.
- assertEquals(appBounds.width(), appBounds.height());
- // The decor height should be a part of the effective bounds.
- assertEquals(mActivity.getBounds().height(), appBounds.height() + decorHeight);
-
- mTask.getConfiguration().windowConfiguration.setRotation(ROTATION_90);
- mActivity.onConfigurationChanged(mTask.getConfiguration());
- // After changing orientation, the aspect ratio should be the same.
- assertEquals(appBounds.width(), appBounds.height());
- // The decor height will be included in width.
- assertEquals(mActivity.getBounds().width(), appBounds.width() + decorHeight);
- }
-
- @Test
- public void testSizeCompatMode_FixedScreenConfigurationWhenMovingToDisplay() {
- // Initialize different bounds on a new display.
- final Rect newDisplayBounds = new Rect(0, 0, 1000, 2000);
- DisplayInfo info = new DisplayInfo();
- mService.mContext.getDisplay().getDisplayInfo(info);
- info.logicalWidth = newDisplayBounds.width();
- info.logicalHeight = newDisplayBounds.height();
- info.logicalDensityDpi = 300;
-
- final ActivityDisplay newDisplay =
- addNewActivityDisplayAt(info, ActivityDisplay.POSITION_TOP);
-
- final Configuration c =
- new Configuration(mStack.getDisplay().getRequestedOverrideConfiguration());
- c.densityDpi = 200;
- mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);
- mActivity = new ActivityBuilder(mService)
- .setTask(mTask)
- .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
- .setMaxAspectRatio(1.5f)
- .build();
- mActivity.visible = true;
-
- final Rect originalBounds = new Rect(mActivity.getBounds());
- final int originalDpi = mActivity.getConfiguration().densityDpi;
-
- // Move the non-resizable activity to the new display.
- mStack.reparent(newDisplay.mDisplayContent, true /* onTop */);
-
- assertEquals(originalBounds.width(),
- mActivity.getWindowConfiguration().getBounds().width());
- assertEquals(originalBounds.height(),
- mActivity.getWindowConfiguration().getBounds().height());
- assertEquals(originalDpi, mActivity.getConfiguration().densityDpi);
- assertTrue(mActivity.inSizeCompatMode());
- }
-
- @Test
- public void testSizeCompatMode_FixedScreenBoundsWhenDisplaySizeChanged() {
- setupDisplayContentForCompatDisplayInsets();
- when(mActivity.getRequestedOrientation()).thenReturn(
- ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- mTask.getWindowConfiguration().setAppBounds(mStack.getDisplay().getBounds());
- mTask.getRequestedOverrideConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT;
- mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
- mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
- mActivity.visible = true;
-
- ensureActivityConfiguration();
- final Rect originalBounds = new Rect(mActivity.getBounds());
-
- // Change the size of current display.
- setupDisplayAndParentSize(1000, 2000);
- ensureActivityConfiguration();
-
- assertEquals(originalBounds.width(),
- mActivity.getWindowConfiguration().getBounds().width());
- assertEquals(originalBounds.height(),
- mActivity.getWindowConfiguration().getBounds().height());
- assertTrue(mActivity.inSizeCompatMode());
- }
-
- @Test
- public void testSizeCompatMode_FixedScreenLayoutSizeBits() {
- final int fixedScreenLayout = Configuration.SCREENLAYOUT_LONG_NO
- | Configuration.SCREENLAYOUT_SIZE_NORMAL;
- final int layoutMask = Configuration.SCREENLAYOUT_LONG_MASK
- | Configuration.SCREENLAYOUT_SIZE_MASK
- | Configuration.SCREENLAYOUT_LAYOUTDIR_MASK;
- mTask.getRequestedOverrideConfiguration().screenLayout = fixedScreenLayout
- | Configuration.SCREENLAYOUT_LAYOUTDIR_LTR;
- prepareFixedAspectRatioUnresizableActivity();
-
- // The initial configuration should inherit from parent.
- assertEquals(mTask.getConfiguration().screenLayout & layoutMask,
- mActivity.getConfiguration().screenLayout & layoutMask);
-
- mTask.getConfiguration().screenLayout = Configuration.SCREENLAYOUT_LAYOUTDIR_RTL
- | Configuration.SCREENLAYOUT_LONG_YES | Configuration.SCREENLAYOUT_SIZE_LARGE;
- mActivity.onConfigurationChanged(mTask.getConfiguration());
-
- // The size and aspect ratio bits don't change, but the layout direction should be updated.
- assertEquals(fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_RTL,
- mActivity.getConfiguration().screenLayout & layoutMask);
- }
-
- @Test
- public void testSizeCompatMode_ResetNonVisibleActivity() {
- final ActivityDisplay display = mStack.getDisplay();
- spyOn(display);
-
- prepareFixedAspectRatioUnresizableActivity();
- mActivity.setState(STOPPED, "testSizeCompatMode");
- mActivity.visible = false;
- mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
- // Make the parent bounds to be different so the activity is in size compatibility mode.
- setupDisplayAndParentSize(600, 1200);
-
- // Simulate the display changes orientation.
- doReturn(ActivityInfo.CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION
- | ActivityInfo.CONFIG_WINDOW_CONFIGURATION)
- .when(display).getLastOverrideConfigurationChanges();
- mActivity.onConfigurationChanged(mTask.getConfiguration());
- when(display.getLastOverrideConfigurationChanges()).thenCallRealMethod();
- // The override configuration should not change so it is still in size compatibility mode.
- assertTrue(mActivity.inSizeCompatMode());
-
- // Change display density
- final DisplayContent displayContent = mStack.getDisplay().mDisplayContent;
- displayContent.mBaseDisplayDensity = (int) (0.7f * displayContent.mBaseDisplayDensity);
- final Configuration c = new Configuration();
- displayContent.computeScreenConfiguration(c);
- mService.mAmInternal = mock(ActivityManagerInternal.class);
- mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);
-
- // The override configuration should be reset and the activity's process will be killed.
- assertFalse(mActivity.inSizeCompatMode());
- verify(mActivity).restartProcessIfVisible();
- waitHandlerIdle(mService.mH);
- verify(mService.mAmInternal).killProcess(
- eq(mActivity.app.mName), eq(mActivity.app.mUid), anyString());
- }
-
- @Test
public void testTakeOptions() {
ActivityOptions opts = ActivityOptions.makeRemoteAnimation(
new RemoteAnimationAdapter(new Stub() {
@@ -1340,33 +1102,4 @@
verify(mActivity).removeFromHistory(anyString());
}
-
- /** Setup {@link #mActivity} as a size-compat-mode-able activity without fixed orientation. */
- private void prepareFixedAspectRatioUnresizableActivity() {
- setupDisplayContentForCompatDisplayInsets();
- mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
- mActivity.info.maxAspectRatio = 1.5f;
- mActivity.visible = true;
- ensureActivityConfiguration();
- }
-
- private void setupDisplayContentForCompatDisplayInsets() {
- final Rect displayBounds = mStack.getDisplay().getBounds();
- setupDisplayAndParentSize(displayBounds.width(), displayBounds.height());
- }
-
- private DisplayContent setupDisplayAndParentSize(int width, int height) {
- final DisplayContent displayContent = mStack.getDisplay().mDisplayContent;
- displayContent.mBaseDisplayWidth = width;
- displayContent.mBaseDisplayHeight = height;
- final Configuration c =
- new Configuration(mStack.getDisplay().getRequestedOverrideConfiguration());
- c.windowConfiguration.setBounds(new Rect(0, 0, width, height));
- c.windowConfiguration.setAppBounds(0, 0, width, height);
- c.windowConfiguration.setRotation(ROTATION_0);
- c.orientation = width > height
- ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT;
- mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);
- return displayContent;
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 9f4143f..21f6c3c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -715,14 +715,10 @@
activity.setRequestedOrientation(newOrientation);
- final ArgumentCaptor<Configuration> captor = ArgumentCaptor.forClass(Configuration.class);
- verify(dc.mActivityDisplay).updateDisplayOverrideConfigurationLocked(captor.capture(),
- same(activity), anyBoolean(), same(null));
- final Configuration newDisplayConfig = captor.getValue();
final int expectedOrientation = newOrientation == SCREEN_ORIENTATION_PORTRAIT
? Configuration.ORIENTATION_PORTRAIT
: Configuration.ORIENTATION_LANDSCAPE;
- assertEquals(expectedOrientation, newDisplayConfig.orientation);
+ assertEquals(expectedOrientation, dc.getConfiguration().orientation);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
new file mode 100644
index 0000000..c9331af
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.TaskStackListener;
+import android.app.WindowConfiguration;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for Size Compatibility mode.
+ *
+ * Build/Install/Run:
+ * atest WmTests:SizeCompatTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class SizeCompatTests extends ActivityTestsBase {
+ private ActivityStack mStack;
+ private Task mTask;
+ private ActivityRecord mActivity;
+
+ private void setUpApp(ActivityDisplay display) {
+ mStack = new StackBuilder(mRootActivityContainer).setDisplay(display).build();
+ mTask = mStack.getChildAt(0);
+ mActivity = mTask.getTopActivity();
+ }
+
+ private void ensureActivityConfiguration() {
+ mActivity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
+ }
+
+ @Test
+ public void testRestartProcessIfVisible() {
+ setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+ doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
+ mActivity.visible = true;
+ mActivity.setSavedState(null /* savedState */);
+ mActivity.setState(ActivityStack.ActivityState.RESUMED, "testRestart");
+ prepareUnresizable(1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
+
+ final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
+ resizeDisplay(mStack.getDisplay(), 600, 1200);
+ // The visible activity should recompute configuration according to the last parent bounds.
+ mService.restartActivityProcessIfVisible(mActivity.appToken);
+
+ assertEquals(ActivityStack.ActivityState.RESTARTING_PROCESS, mActivity.getState());
+ assertNotEquals(originalOverrideBounds, mActivity.getBounds());
+ }
+
+ @Test
+ public void testKeepBoundsWhenChangingFromFreeformToFullscreen() {
+ removeGlobalMinSizeRestriction();
+ // create freeform display and a freeform app
+ ActivityDisplay display = new TestActivityDisplay.Builder(mService, 2000, 1000)
+ .setCanRotate(false)
+ .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM).build();
+ setUpApp(display);
+
+ // Put app window into freeform and then make it a compat app.
+ mTask.setBounds(100, 100, 400, 600);
+ prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+
+ final Rect bounds = new Rect(mActivity.getBounds());
+ final int density = mActivity.getConfiguration().densityDpi;
+
+ // change display configuration to fullscreen
+ Configuration c = new Configuration(display.getRequestedOverrideConfiguration());
+ c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+ display.onRequestedOverrideConfigurationChanged(c);
+
+ // check if dimensions stay the same
+ assertTrue(mActivity.inSizeCompatMode());
+ assertEquals(bounds.width(), mActivity.getBounds().width());
+ assertEquals(bounds.height(), mActivity.getBounds().height());
+ assertEquals(density, mActivity.getConfiguration().densityDpi);
+ }
+
+ @Test
+ public void testFixedAspectRatioBoundsWithDecor() {
+ final int decorHeight = 200; // e.g. The device has cutout.
+ setUpApp(new TestActivityDisplay.Builder(mService, 600, 800)
+ .setNotch(decorHeight).build());
+
+ mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1;
+ prepareUnresizable(-1f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+ // The parent configuration doesn't change since the first resolved configuration, so the
+ // activity shouldn't be in the size compatibility mode.
+ assertFalse(mActivity.inSizeCompatMode());
+
+ final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds();
+ // Ensure the app bounds keep the declared aspect ratio.
+ assertEquals(appBounds.width(), appBounds.height());
+ // The decor height should be a part of the effective bounds.
+ assertEquals(mActivity.getBounds().height(), appBounds.height() + decorHeight);
+
+ mTask.getConfiguration().windowConfiguration.setRotation(ROTATION_90);
+ mActivity.onConfigurationChanged(mTask.getConfiguration());
+ // After changing orientation, the aspect ratio should be the same.
+ assertEquals(appBounds.width(), appBounds.height());
+ // The decor height will be included in width.
+ assertEquals(mActivity.getBounds().width(), appBounds.width() + decorHeight);
+ }
+
+ @Test
+ public void testFixedScreenConfigurationWhenMovingToDisplay() {
+ setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+
+ // Make a new less-tall display with lower density
+ final ActivityDisplay newDisplay =
+ new TestActivityDisplay.Builder(mService, 1000, 2000)
+ .setDensityDpi(200).build();
+
+ mActivity = new ActivityBuilder(mService)
+ .setTask(mTask)
+ .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
+ .setMaxAspectRatio(1.5f)
+ .build();
+ mActivity.visible = true;
+
+ final Rect originalBounds = new Rect(mActivity.getBounds());
+ final int originalDpi = mActivity.getConfiguration().densityDpi;
+
+ // Move the non-resizable activity to the new display.
+ mStack.reparent(newDisplay.mDisplayContent, true /* onTop */);
+
+ assertEquals(originalBounds.width(), mActivity.getBounds().width());
+ assertEquals(originalBounds.height(), mActivity.getBounds().height());
+ assertEquals(originalDpi, mActivity.getConfiguration().densityDpi);
+ assertTrue(mActivity.inSizeCompatMode());
+ }
+
+ @Test
+ public void testFixedScreenBoundsWhenDisplaySizeChanged() {
+ setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+ prepareUnresizable(-1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+ assertFalse(mActivity.inSizeCompatMode());
+
+ final Rect origBounds = new Rect(mActivity.getBounds());
+
+ // Change the size of current display.
+ resizeDisplay(mStack.getDisplay(), 1000, 2000);
+ ensureActivityConfiguration();
+
+ assertEquals(origBounds.width(), mActivity.getWindowConfiguration().getBounds().width());
+ assertEquals(origBounds.height(), mActivity.getWindowConfiguration().getBounds().height());
+ assertTrue(mActivity.inSizeCompatMode());
+
+ // Change display size to a different orientation
+ resizeDisplay(mStack.getDisplay(), 2000, 1000);
+ ensureActivityConfiguration();
+ assertEquals(origBounds.width(), mActivity.getWindowConfiguration().getBounds().width());
+ assertEquals(origBounds.height(), mActivity.getWindowConfiguration().getBounds().height());
+ }
+
+ @Test
+ public void testLetterboxFullscreenBounds() {
+ setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+
+ // Fill out required fields on default display since WM-side is mocked out
+ prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE);
+ assertFalse(mActivity.inSizeCompatMode());
+ assertTrue(mActivity.getBounds().width() > mActivity.getBounds().height());
+ }
+
+ @Test
+ public void testMoveToDifferentOrientDisplay() {
+ setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+
+ final ActivityDisplay newDisplay =
+ new TestActivityDisplay.Builder(mService, 2000, 1000)
+ .setCanRotate(false).build();
+
+ prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+ assertFalse(mActivity.inSizeCompatMode());
+
+ final Rect origBounds = new Rect(mActivity.getBounds());
+
+ // Move the non-resizable activity to the new display.
+ mStack.reparent(newDisplay.mDisplayContent, true /* onTop */);
+ ensureActivityConfiguration();
+ assertEquals(origBounds.width(), mActivity.getWindowConfiguration().getBounds().width());
+ assertEquals(origBounds.height(), mActivity.getWindowConfiguration().getBounds().height());
+ assertTrue(mActivity.inSizeCompatMode());
+ }
+
+ @Test
+ public void testFixedOrientRotateCutoutDisplay() {
+ // Create a display with a notch/cutout
+ setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).setNotch(60).build());
+ prepareUnresizable(1.4f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+
+ final Rect origBounds = new Rect(mActivity.getBounds());
+ final Rect origAppBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds());
+
+ // Rotate the display
+ Configuration c = new Configuration();
+ mStack.getDisplay().mDisplayContent.getDisplayRotation().setRotation(ROTATION_270);
+ mStack.getDisplay().mDisplayContent.computeScreenConfiguration(c);
+ mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);
+
+ // Make sure the app size is the same
+ assertEquals(ROTATION_270, mStack.getWindowConfiguration().getRotation());
+ assertEquals(origBounds.width(), mActivity.getWindowConfiguration().getBounds().width());
+ assertEquals(origBounds.height(), mActivity.getWindowConfiguration().getBounds().height());
+ assertEquals(origAppBounds.width(),
+ mActivity.getWindowConfiguration().getAppBounds().width());
+ assertEquals(origAppBounds.height(),
+ mActivity.getWindowConfiguration().getAppBounds().height());
+ }
+
+ @Test
+ public void testFixedAspOrientChangeOrient() {
+ setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+
+ prepareUnresizable(1.4f /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE);
+ assertTrue(mActivity.inSizeCompatMode());
+
+ final Rect originalBounds = new Rect(mActivity.getBounds());
+ final Rect originalAppBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds());
+
+ // Change the fixed orientation
+ mActivity.mOrientation = SCREEN_ORIENTATION_PORTRAIT;
+ mActivity.info.screenOrientation = SCREEN_ORIENTATION_PORTRAIT;
+ // TaskRecord's configuration actually depends on the activity config right now for
+ // pillarboxing.
+ mActivity.getTask().onRequestedOverrideConfigurationChanged(
+ mActivity.getTask().getRequestedOverrideConfiguration());
+
+ assertEquals(originalBounds.width(), mActivity.getBounds().height());
+ assertEquals(originalBounds.height(), mActivity.getBounds().width());
+ assertEquals(originalAppBounds.width(),
+ mActivity.getWindowConfiguration().getAppBounds().height());
+ assertEquals(originalAppBounds.height(),
+ mActivity.getWindowConfiguration().getAppBounds().width());
+ }
+
+ @Test
+ public void testFixedScreenLayoutSizeBits() {
+ setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+ final int fixedScreenLayout = Configuration.SCREENLAYOUT_LONG_NO
+ | Configuration.SCREENLAYOUT_SIZE_NORMAL;
+ final int layoutMask = Configuration.SCREENLAYOUT_LONG_MASK
+ | Configuration.SCREENLAYOUT_SIZE_MASK
+ | Configuration.SCREENLAYOUT_LAYOUTDIR_MASK;
+ Configuration c = new Configuration(mTask.getRequestedOverrideConfiguration());
+ c.screenLayout = fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_LTR;
+ mTask.onRequestedOverrideConfigurationChanged(c);
+ prepareUnresizable(1.5f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+ // The initial configuration should inherit from parent.
+ assertEquals(fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_LTR,
+ mActivity.getConfiguration().screenLayout & layoutMask);
+
+ mTask.getConfiguration().screenLayout = Configuration.SCREENLAYOUT_LAYOUTDIR_RTL
+ | Configuration.SCREENLAYOUT_LONG_YES | Configuration.SCREENLAYOUT_SIZE_LARGE;
+ mActivity.onConfigurationChanged(mTask.getConfiguration());
+
+ // The size and aspect ratio bits don't change, but the layout direction should be updated.
+ assertEquals(fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_RTL,
+ mActivity.getConfiguration().screenLayout & layoutMask);
+ }
+
+ @Test
+ public void testResetNonVisibleActivity() {
+ setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
+ final ActivityDisplay display = mStack.getDisplay();
+ spyOn(display);
+
+ prepareUnresizable(1.5f, SCREEN_ORIENTATION_UNSPECIFIED);
+ mActivity.setState(STOPPED, "testSizeCompatMode");
+ mActivity.visible = false;
+ mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
+ // Make the parent bounds to be different so the activity is in size compatibility mode.
+ mTask.getWindowConfiguration().setAppBounds(new Rect(0, 0, 600, 1200));
+
+ // Simulate the display changes orientation.
+ when(display.getLastOverrideConfigurationChanges()).thenReturn(
+ ActivityInfo.CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION
+ | ActivityInfo.CONFIG_WINDOW_CONFIGURATION);
+ mActivity.onConfigurationChanged(mTask.getConfiguration());
+ when(display.getLastOverrideConfigurationChanges()).thenCallRealMethod();
+ // The override configuration should not change so it is still in size compatibility mode.
+ assertTrue(mActivity.inSizeCompatMode());
+
+ // Change display density
+ final DisplayContent displayContent = mStack.getDisplay().mDisplayContent;
+ displayContent.mBaseDisplayDensity = (int) (0.7f * displayContent.mBaseDisplayDensity);
+ final Configuration c = new Configuration();
+ displayContent.computeScreenConfiguration(c);
+ mService.mAmInternal = mock(ActivityManagerInternal.class);
+ mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);
+
+ // The override configuration should be reset and the activity's process will be killed.
+ assertFalse(mActivity.inSizeCompatMode());
+ verify(mActivity).restartProcessIfVisible();
+ mLockRule.runWithScissors(mService.mH, () -> { }, TimeUnit.SECONDS.toMillis(3));
+ verify(mService.mAmInternal).killProcess(
+ eq(mActivity.app.mName), eq(mActivity.app.mUid), anyString());
+ }
+
+ /**
+ * Ensures that {@link TaskStackListener} can receive callback about the activity in size
+ * compatibility mode.
+ */
+ @Test
+ public void testHandleActivitySizeCompatMode() {
+ setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2000).build());
+ ActivityRecord activity = mActivity;
+ activity.setState(ActivityStack.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
+ prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+ ensureActivityConfiguration();
+ assertFalse(mActivity.inSizeCompatMode());
+
+ final ArrayList<IBinder> compatTokens = new ArrayList<>();
+ mService.getTaskChangeNotificationController().registerTaskStackListener(
+ new TaskStackListener() {
+ @Override
+ public void onSizeCompatModeActivityChanged(int displayId,
+ IBinder activityToken) {
+ compatTokens.add(activityToken);
+ }
+ });
+
+ // Resize the display so that the activity exercises size-compat mode.
+ resizeDisplay(mStack.getDisplay(), 1000, 2500);
+
+ // Expect the exact token when the activity is in size compatibility mode.
+ assertEquals(1, compatTokens.size());
+ assertEquals(activity.appToken, compatTokens.get(0));
+
+ compatTokens.clear();
+ // Make the activity resizable again by restarting it
+ activity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+ activity.visible = true;
+ activity.restartProcessIfVisible();
+ // The full lifecycle isn't hooked up so manually set state to resumed
+ activity.setState(ActivityStack.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
+ mStack.getDisplay().handleActivitySizeCompatModeIfNeeded(activity);
+
+ // Expect null token when switching to non-size-compat mode activity.
+ assertEquals(1, compatTokens.size());
+ assertEquals(null, compatTokens.get(0));
+ }
+
+ /**
+ * Setup {@link #mActivity} as a size-compat-mode-able activity with fixed aspect and/or
+ * orientation.
+ */
+ private void prepareUnresizable(float maxAspect, int screenOrientation) {
+ mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+ mActivity.visible = true;
+ if (maxAspect >= 0) {
+ mActivity.info.maxAspectRatio = maxAspect;
+ }
+ if (screenOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+ mActivity.mOrientation = screenOrientation;
+ mActivity.info.screenOrientation = screenOrientation;
+ // TaskRecord's configuration actually depends on the activity config right now for
+ // pillarboxing.
+ mActivity.getTask().onRequestedOverrideConfigurationChanged(
+ mActivity.getTask().getRequestedOverrideConfiguration());
+ }
+ ensureActivityConfiguration();
+ }
+
+ private void resizeDisplay(ActivityDisplay display, int width, int height) {
+ final DisplayContent displayContent = display.mDisplayContent;
+ displayContent.mBaseDisplayWidth = width;
+ displayContent.mBaseDisplayHeight = height;
+ Configuration c = new Configuration();
+ displayContent.computeScreenConfiguration(c);
+ display.onRequestedOverrideConfigurationChanged(c);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java b/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
index 9c3ff65..48ec261 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
@@ -23,12 +23,17 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.mockito.ArgumentMatchers.any;
+import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
import android.view.Display;
+import android.view.DisplayCutout;
import android.view.DisplayInfo;
class TestActivityDisplay extends ActivityDisplay {
@@ -107,4 +112,94 @@
.setCreateActivity(false)
.build();
}
+
+ public static class Builder {
+ private final DisplayInfo mInfo;
+ private boolean mCanRotate = true;
+ private int mWindowingMode = WINDOWING_MODE_FULLSCREEN;
+ private int mPosition = POSITION_TOP;
+ private final ActivityTaskManagerService mService;
+ private boolean mSystemDecorations = false;
+
+ Builder(ActivityTaskManagerService service, int width, int height) {
+ mService = service;
+ mInfo = new DisplayInfo();
+ mService.mContext.getDisplay().getDisplayInfo(mInfo);
+ mInfo.logicalWidth = width;
+ mInfo.logicalHeight = height;
+ mInfo.logicalDensityDpi = 300;
+ mInfo.displayCutout = null;
+ }
+ Builder(ActivityTaskManagerService service, DisplayInfo info) {
+ mService = service;
+ mInfo = info;
+ }
+ Builder setSystemDecorations(boolean yes) {
+ mSystemDecorations = yes;
+ return this;
+ }
+ Builder setPosition(int position) {
+ mPosition = position;
+ return this;
+ }
+ Builder setUniqueId(String uniqueId) {
+ mInfo.uniqueId = uniqueId;
+ return this;
+ }
+ Builder setType(int type) {
+ mInfo.type = type;
+ return this;
+ }
+ Builder setOwnerUid(int ownerUid) {
+ mInfo.ownerUid = ownerUid;
+ return this;
+ }
+ Builder setNotch(int height) {
+ mInfo.displayCutout = new DisplayCutout(
+ Insets.of(0, height, 0, 0), null, new Rect(20, 0, 80, height), null, null);
+ return this;
+ }
+ Builder setCanRotate(boolean canRotate) {
+ mCanRotate = canRotate;
+ return this;
+ }
+ Builder setWindowingMode(int windowingMode) {
+ mWindowingMode = windowingMode;
+ return this;
+ }
+ Builder setDensityDpi(int dpi) {
+ mInfo.logicalDensityDpi = dpi;
+ return this;
+ }
+ TestActivityDisplay build() {
+ final int displayId = SystemServicesTestRule.sNextDisplayId++;
+ final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
+ mInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
+ final TestActivityDisplay newDisplay;
+ synchronized (mService.mGlobalLock) {
+ newDisplay = new TestActivityDisplay(mService.mStackSupervisor, display);
+ mService.mRootActivityContainer.addChild(newDisplay, mPosition);
+ }
+ // disable the normal system decorations
+ final DisplayPolicy displayPolicy = newDisplay.mDisplayContent.getDisplayPolicy();
+ spyOn(displayPolicy);
+ if (mSystemDecorations) {
+ doReturn(true).when(newDisplay).supportsSystemDecorations();
+ } else {
+ doReturn(false).when(displayPolicy).hasNavigationBar();
+ doReturn(false).when(displayPolicy).hasStatusBar();
+ doReturn(false).when(newDisplay).supportsSystemDecorations();
+ }
+ Configuration c = new Configuration();
+ newDisplay.mDisplayContent.computeScreenConfiguration(c);
+ c.windowConfiguration.setWindowingMode(mWindowingMode);
+ newDisplay.onRequestedOverrideConfigurationChanged(c);
+ // This is a rotating display
+ if (mCanRotate) {
+ doReturn(false).when(newDisplay.mDisplayContent)
+ .handlesOrientationChangeFromDescendant();
+ }
+ return newDisplay;
+ }
+ }
}