Separate and add more SizeCompat tests; add Display Builder

The number of SizeCompat tests is growing so this
separates them out into their own class. Additionally,
it streamlines display and activity creation for the
variety of scenarios that need testing and facilitates
consistent display creation for tests (previous code
would create displays based on the device display which
meant the tests would potentially test different things
on different devices).

Also added a Builder to TestActivityDisplay. This
makes it possible to create displays with/without notch
and custom windowingmode/rotatibility/density/etc.

Also fixed a small bug where aspect-ratio was calculated
incorrectly.

Some SizeCompat tests were added too:
 testFixedAspOrientChangeOrient to verify that a sizecompat
   app can still change fixed orientation properly
 testMoveToDifferentOrientDisplay to verify that a sizecompat
   app properly moves between different non-rotateble displays
 testLetterboxFullscreenBounds to sanity check that sizecompat
   apps start properly when letterboxed by policy
 testFixedOrientRotateCutoutDisplay to verify that a sizecompat
   app stays fixed when on a rotating display with cutout.

Bug: 138594779
Test: this
Change-Id: Ic78374ae9e58297c609349c382efc6738521872c
This commit is contained in:
Evan Rosky
2019-09-11 17:28:07 -07:00
parent 822b2f4d7a
commit 0177507be7
6 changed files with 523 additions and 329 deletions

View File

@@ -6171,8 +6171,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// 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;

View File

@@ -38,12 +38,7 @@ import static org.mockito.ArgumentMatchers.any;
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 androidx.test.filters.SmallTest;
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 @@ public class ActivityDisplayTests extends ActivityTestsBase {
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));
}
}

View File

@@ -18,24 +18,18 @@ package com.android.server.wm;
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 com.google.common.truth.Truth.assertThat;
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.ComponentName;
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;
@@ -218,23 +205,6 @@ public class ActivityRecordTests extends ActivityTestsBase {
assertFalse(activity.canBeLaunchedOnDisplay(DEFAULT_DISPLAY + 1));
}
@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();
@@ -483,214 +453,6 @@ public class ActivityRecordTests extends ActivityTestsBase {
assertEquals(STARTED, mActivity.getState());
}
@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(
@@ -1340,33 +1102,4 @@ public class ActivityRecordTests extends ActivityTestsBase {
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;
}
}

View File

@@ -715,14 +715,10 @@ public class DisplayContentTests extends WindowTestsBase {
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

View File

@@ -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);
}
}

View File

@@ -23,12 +23,17 @@ import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
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 @@ class TestActivityDisplay extends ActivityDisplay {
.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;
}
}
}