From 93281edad3583226c981152aa97195a398709e51 Mon Sep 17 00:00:00 2001 From: Naomi Musgrave Date: Thu, 25 Feb 2021 15:44:15 +0000 Subject: [PATCH 1/2] Sandbox letterbox and size compat apps Sandbox Display#getRealSize and WindowManager bounds when letterbox or size compat mode are applied to the configuration. Display uses this field to provide the sandboxed display size. This reverts commit 0ba6185639c20ee8a55cf697aad00716b14d22e9. Test: atest WindowConfigurationTests Test: atest FrameworksMockingCoreTests:android.view.DisplayTests Test: atest WmTests:SizeCompatTests Bug: 181219241 Change-Id: I86e8edb8368269da8e02cf34f429d245550666c6 --- core/java/android/view/Display.java | 48 +- core/java/android/view/DisplayInfo.java | 19 + .../src/android/view/DisplayTests.java | 531 ++++++++++++++++++ data/etc/services.core.protolog.json | 6 + .../com/android/server/wm/ActivityRecord.java | 32 ++ .../server/wm/ConfigurationContainer.java | 3 +- .../android/server/wm/SizeCompatTests.java | 178 ++++-- .../android/server/wm/TestDisplayContent.java | 1 + 8 files changed, 777 insertions(+), 41 deletions(-) create mode 100644 core/tests/mockingcoretests/src/android/view/DisplayTests.java diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index d138b4b414505..229e74b262b8b 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -25,6 +25,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.KeyguardManager; +import android.app.WindowConfiguration; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -678,9 +679,9 @@ public final class Display { @UnsupportedAppUsage public DisplayAdjustments getDisplayAdjustments() { if (mResources != null) { - final DisplayAdjustments currentAdjustements = mResources.getDisplayAdjustments(); - if (!mDisplayAdjustments.equals(currentAdjustements)) { - mDisplayAdjustments = new DisplayAdjustments(currentAdjustements); + final DisplayAdjustments currentAdjustments = mResources.getDisplayAdjustments(); + if (!mDisplayAdjustments.equals(currentAdjustments)) { + mDisplayAdjustments = new DisplayAdjustments(currentAdjustments); } } @@ -1278,6 +1279,18 @@ public final class Display { public void getRealSize(Point outSize) { synchronized (this) { updateDisplayInfoLocked(); + if (shouldReportMaxBounds()) { + final Rect bounds = mResources.getConfiguration() + .windowConfiguration.getMaxBounds(); + outSize.x = bounds.width(); + outSize.y = bounds.height(); + if (DEBUG) { + Log.d(TAG, "getRealSize determined from max bounds: " + outSize); + } + // Skip adjusting by fixed rotation, since if it is necessary, the configuration + // should already reflect the expected rotation. + return; + } outSize.x = mDisplayInfo.logicalWidth; outSize.y = mDisplayInfo.logicalHeight; if (mMayAdjustByFixedRotation) { @@ -1336,6 +1349,17 @@ public final class Display { public void getRealMetrics(DisplayMetrics outMetrics) { synchronized (this) { updateDisplayInfoLocked(); + if (shouldReportMaxBounds()) { + mDisplayInfo.getMaxBoundsMetrics(outMetrics, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, + mResources.getConfiguration()); + if (DEBUG) { + Log.d(TAG, "getRealMetrics determined from max bounds: " + outMetrics); + } + // Skip adjusting by fixed rotation, since if it is necessary, the configuration + // should already reflect the expected rotation. + return; + } mDisplayInfo.getLogicalMetrics(outMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); if (mMayAdjustByFixedRotation) { @@ -1344,6 +1368,24 @@ public final class Display { } } + /** + * Determines if {@link WindowConfiguration#getMaxBounds()} should be reported as the + * display dimensions. The max bounds field may be smaller than the logical dimensions + * when apps need to be sandboxed. + * + * Depends upon {@link WindowConfiguration#getMaxBounds()} being set in + * {@link com.android.server.wm.ConfigurationContainer#providesMaxBounds()}. In most cases, this + * value reflects the size of the current DisplayArea. + * @return {@code true} when max bounds should be applied. + */ + private boolean shouldReportMaxBounds() { + if (mResources == null) { + return false; + } + final Configuration config = mResources.getConfiguration(); + return config != null && !config.windowConfiguration.getMaxBounds().isEmpty(); + } + /** * Gets the state of the display, such as whether it is on or off. * diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 655f42308a1f8..a8aaeb7846a21 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -24,6 +24,7 @@ import static android.view.DisplayInfoProto.LOGICAL_WIDTH; import static android.view.DisplayInfoProto.NAME; import android.annotation.Nullable; +import android.app.WindowConfiguration; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -615,11 +616,29 @@ public final class DisplayInfo implements Parcelable { getMetricsWithSize(outMetrics, ci, configuration, appWidth, appHeight); } + /** + * Populates {@code outMetrics} with details of the logical display. Bounds are limited + * by the logical size of the display. + * + * @param outMetrics the {@link DisplayMetrics} to be populated + * @param compatInfo the {@link CompatibilityInfo} to be applied + * @param configuration the {@link Configuration} + */ public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo, Configuration configuration) { getMetricsWithSize(outMetrics, compatInfo, configuration, logicalWidth, logicalHeight); } + /** + * Similar to {@link #getLogicalMetrics}, but the limiting bounds are determined from + * {@link WindowConfiguration#getMaxBounds()} + */ + public void getMaxBoundsMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo, + Configuration configuration) { + Rect bounds = configuration.windowConfiguration.getMaxBounds(); + getMetricsWithSize(outMetrics, compatInfo, configuration, bounds.width(), bounds.height()); + } + public int getNaturalWidth() { return rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 ? logicalWidth : logicalHeight; diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTests.java b/core/tests/mockingcoretests/src/android/view/DisplayTests.java new file mode 100644 index 0000000000000..678f21fe1211b --- /dev/null +++ b/core/tests/mockingcoretests/src/android/view/DisplayTests.java @@ -0,0 +1,531 @@ +/* + * Copyright (C) 2021 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 android.view; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_90; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.display.DisplayManagerGlobal; +import android.platform.test.annotations.Presubmit; +import android.util.DisplayMetrics; +import android.view.DisplayAdjustments.FixedRotationAdjustments; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.dx.mockito.inline.extended.StaticMockitoSession; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.quality.Strictness; + +import java.util.function.Consumer; + +/** + * Tests for {@link Display}. + * + *

Build/Install/Run: + * + * atest FrameworksMockingCoreTests:android.view.DisplayTests + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class DisplayTests { + + private static final int APP_WIDTH = 272; + private static final int APP_HEIGHT = 700; + // Tablet size device, ROTATION_0 corresponds to portrait. + private static final int LOGICAL_WIDTH = 700; + private static final int LOGICAL_HEIGHT = 1800; + + // Bounds of the app when the device is in portrait mode. + private static Rect sAppBoundsPortrait = buildAppBounds(LOGICAL_WIDTH, LOGICAL_HEIGHT); + private static Rect sAppBoundsLandscape = buildAppBounds(LOGICAL_HEIGHT, LOGICAL_WIDTH); + + private StaticMockitoSession mMockitoSession; + + private DisplayManagerGlobal mDisplayManagerGlobal; + private Context mApplicationContext; + private DisplayInfo mDisplayInfo = new DisplayInfo(); + + @Before + public void setupTests() { + mMockitoSession = mockitoSession() + .mockStatic(DisplayManagerGlobal.class) + .strictness(Strictness.LENIENT) + .startMocking(); + + // Ensure no adjustments are set before each test. + mApplicationContext = ApplicationProvider.getApplicationContext(); + DisplayAdjustments displayAdjustments = + mApplicationContext.getResources().getDisplayAdjustments(); + displayAdjustments.setFixedRotationAdjustments(null); + mApplicationContext.getResources().overrideDisplayAdjustments(null); + mApplicationContext.getResources().getConfiguration().windowConfiguration.setAppBounds( + null); + mApplicationContext.getResources().getConfiguration().windowConfiguration.setMaxBounds( + null); + mDisplayInfo.rotation = ROTATION_0; + + mDisplayManagerGlobal = mock(DisplayManagerGlobal.class); + doReturn(mDisplayInfo).when(mDisplayManagerGlobal).getDisplayInfo(anyInt()); + } + + @After + public void teardownTests() { + if (mMockitoSession != null) { + mMockitoSession.finishMocking(); + } + Mockito.framework().clearInlineMocks(); + } + + @Test + public void testConstructor_defaultDisplayAdjustments_matchesDisplayInfo() { + setDisplayInfoPortrait(mDisplayInfo); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + assertThat(display.getDisplayAdjustments()).isEqualTo( + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + DisplayInfo actualDisplayInfo = new DisplayInfo(); + display.getDisplayInfo(actualDisplayInfo); + verifyDisplayInfo(actualDisplayInfo, mDisplayInfo); + } + + @Test + public void testConstructor_defaultResources_matchesDisplayInfo() { + setDisplayInfoPortrait(mDisplayInfo); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + assertThat(display.getDisplayAdjustments()).isEqualTo( + mApplicationContext.getResources().getDisplayAdjustments()); + DisplayInfo actualDisplayInfo = new DisplayInfo(); + display.getDisplayInfo(actualDisplayInfo); + verifyDisplayInfo(actualDisplayInfo, mDisplayInfo); + } + + @Test + public void testGetRotation_defaultDisplayAdjustments_rotationNotAdjusted() { + setDisplayInfoPortrait(mDisplayInfo); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + assertThat(display.getRotation()).isEqualTo(ROTATION_0); + } + + @Test + public void testGetRotation_displayAdjustmentsWithoutOverride_rotationNotAdjusted() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + // GIVEN fixed rotation adjustments are rotated, but no override is set. + DisplayAdjustments displayAdjustments = DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; + final FixedRotationAdjustments fixedRotationAdjustments = + new FixedRotationAdjustments(ROTATION_90, APP_WIDTH, APP_HEIGHT, + DisplayCutout.NO_CUTOUT); + displayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments); + // GIVEN display is constructed with display adjustments. + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + displayAdjustments); + // THEN rotation is not adjusted since no override was set. + assertThat(display.getRotation()).isEqualTo(ROTATION_0); + } + + @Test + public void testGetRotation_resourcesWithoutOverride_rotationNotAdjusted() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + // GIVEN fixed rotation adjustments are rotated, but no override is set. + setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90); + // GIVEN display is constructed with default resources. + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN rotation is not adjusted since no override is set. + assertThat(display.getRotation()).isEqualTo(ROTATION_0); + } + + @Test + public void testGetRotation_resourcesWithOverrideDisplayAdjustments_rotationAdjusted() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + // GIVEN fixed rotation adjustments are rotated, and an override is set. + setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90); + // GIVEN display is constructed with default resources. + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN rotation is adjusted since an override is set. + assertThat(display.getRotation()).isEqualTo(ROTATION_90); + } + + @Test + public void testGetRealSize_defaultResourcesPortrait_matchesLogicalSize() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real size matches display orientation. + verifyRealSizeIsPortrait(display); + } + + @Test + public void testGetRealSize_defaultResourcesLandscape_matchesRotatedLogicalSize() { + // GIVEN display is rotated. + setDisplayInfoLandscape(mDisplayInfo); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real size matches display orientation. + verifyRealSizeIsLandscape(display); + } + + @Test + public void testGetRealSize_defaultDisplayAdjustmentsPortrait_matchesLogicalSize() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + // THEN real size matches display orientation. + verifyRealSizeIsPortrait(display); + } + + @Test + public void testGetRealSize_defaultDisplayAdjustmentsLandscape_matchesLogicalSize() { + // GIVEN display is rotated. + setDisplayInfoLandscape(mDisplayInfo); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + // THEN real size matches display orientation. + verifyRealSizeIsLandscape(display); + } + + @Test + public void testGetRealSize_resourcesPortraitWithFixedRotation_notRotatedLogicalSize() { + // GIVEN display is rotated. + setDisplayInfoLandscape(mDisplayInfo); + // GIVEN fixed rotation adjustments are rotated. + setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0); + // GIVEN display is constructed with default resources. + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real size matches display orientation. + verifyRealSizeIsLandscape(display); + } + + @Test + public void testGetRealSize_resourcesWithLandscapeFixedRotation_notRotatedLogicalSize() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + // GIVEN fixed rotation adjustments are rotated. + setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90); + // GIVEN display is constructed with default resources. + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real size matches display orientation. + verifyRealSizeIsPortrait(display); + } + + @Test + public void testGetRealSize_resourcesWithPortraitOverrideRotation_rotatedLogicalSize() { + // GIVEN display is rotated. + setDisplayInfoLandscape(mDisplayInfo); + // GIVEN fixed rotation adjustments are rotated, and an override is set. + setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0); + // GIVEN display is constructed with default resources. + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real size matches app orientation. + verifyRealSizeIsPortrait(display); + } + + @Test + public void testGetRealSize_resourcesWithLandscapeOverrideRotation_rotatedLogicalSize() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + // GIVEN fixed rotation adjustments are rotated, and an override is set. + setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90); + // GIVEN display is constructed with default resources. + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real size matches app orientation. + verifyRealSizeIsLandscape(display); + } + + @Test + public void testGetRealSize_resourcesPortraitSandboxed_matchesSandboxBounds() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + // GIVEN app is letterboxed. + setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(), + sAppBoundsPortrait); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real size matches app bounds. + verifyRealSizeMatchesApp(display, sAppBoundsPortrait); + } + + @Test + public void testGetRealSize_resourcesLandscapeSandboxed_matchesSandboxBounds() { + // GIVEN display is rotated. + setDisplayInfoLandscape(mDisplayInfo); + // GIVEN app is letterboxed. + setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(), + sAppBoundsLandscape); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real size matches app bounds. + verifyRealSizeMatchesApp(display, sAppBoundsLandscape); + } + + @Test + public void testGetRealMetrics_defaultResourcesPortrait_matchesLogicalSize() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real metrics matches display orientation. + verifyRealMetricsIsPortrait(display); + } + + @Test + public void testGetRealMetrics_defaultResourcesLandscape_matchesRotatedLogicalSize() { + // GIVEN display is rotated. + setDisplayInfoLandscape(mDisplayInfo); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real metrics matches display orientation. + verifyRealMetricsIsLandscape(display); + } + + @Test + public void testGetRealMetrics_defaultDisplayAdjustmentsPortrait_matchesLogicalSize() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + // THEN real metrics matches display orientation. + verifyRealMetricsIsPortrait(display); + } + + @Test + public void testGetRealMetrics_defaultDisplayAdjustmentsLandscape_matchesLogicalSize() { + // GIVEN display is rotated. + setDisplayInfoLandscape(mDisplayInfo); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + // THEN real metrics matches display orientation. + verifyRealMetricsIsLandscape(display); + } + + @Test + public void testGetRealMetrics_resourcesPortraitWithFixedRotation_notRotatedLogicalSize() { + // GIVEN display is rotated. + setDisplayInfoLandscape(mDisplayInfo); + // GIVEN fixed rotation adjustments are rotated. + setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0); + // GIVEN display is constructed with default resources. + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real metrics matches display orientation. + verifyRealMetricsIsLandscape(display); + } + + @Test + public void testGetRealMetrics_resourcesWithLandscapeFixedRotation_notRotatedLogicalSize() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + // GIVEN fixed rotation adjustments are rotated. + setFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90); + // GIVEN display is constructed with default resources. + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real metrics matches display orientation. + verifyRealMetricsIsPortrait(display); + } + + @Test + public void testGetRealMetrics_resourcesWithPortraitOverrideRotation_rotatedLogicalSize() { + // GIVEN display is rotated. + setDisplayInfoLandscape(mDisplayInfo); + // GIVEN fixed rotation adjustments are rotated with an override. + setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_0); + // GIVEN display is constructed with default resources. + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real metrics matches app orientation. + verifyRealMetricsIsPortrait(display); + } + + @Test + public void testGetRealMetrics_resourcesWithLandscapeOverrideRotation_rotatedLogicalSize() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + // GIVEN fixed rotation adjustments are rotated. + setOverrideFixedRotationAdjustments(mApplicationContext.getResources(), ROTATION_90); + // GIVEN display is constructed with default resources. + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real metrics matches app orientation. + verifyRealMetricsIsLandscape(display); + } + + @Test + public void testGetRealMetrics_resourcesPortraitSandboxed_matchesSandboxBounds() { + // GIVEN display is not rotated. + setDisplayInfoPortrait(mDisplayInfo); + // GIVEN app is letterboxed. + setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(), + sAppBoundsPortrait); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real metrics matches app bounds. + verifyRealMetricsMatchesApp(display, sAppBoundsPortrait); + } + + @Test + public void testGetRealMetrics_resourcesLandscapeSandboxed_matchesSandboxBounds() { + // GIVEN display is rotated. + setDisplayInfoLandscape(mDisplayInfo); + // GIVEN app is letterboxed. + setMaxBoundsSandboxedToMatchAppBounds(mApplicationContext.getResources(), + sAppBoundsLandscape); + final Display display = new Display(mDisplayManagerGlobal, DEFAULT_DISPLAY, mDisplayInfo, + mApplicationContext.getResources()); + // THEN real metrics matches app bounds. + verifyRealMetricsMatchesApp(display, sAppBoundsLandscape); + } + + // Given rotated display dimensions, calculate the letterboxed app bounds. + private static Rect buildAppBounds(int displayWidth, int displayHeight) { + final int midWidth = displayWidth / 2; + final int left = midWidth - (APP_WIDTH / 2); + final int right = midWidth + (APP_WIDTH / 2); + final int midHeight = displayHeight / 2; + // Coordinate system starts at top left. + final int top = midHeight - (APP_HEIGHT / 2); + final int bottom = midHeight + (APP_HEIGHT / 2); + return new Rect(left, top, right, bottom); + } + + private static void setDisplayInfoLandscape(DisplayInfo displayInfo) { + displayInfo.rotation = ROTATION_90; + // Flip width & height assignment since the device is rotated. + displayInfo.logicalWidth = LOGICAL_HEIGHT; + displayInfo.logicalHeight = LOGICAL_WIDTH; + } + + private static void setDisplayInfoPortrait(DisplayInfo displayInfo) { + displayInfo.rotation = ROTATION_0; + displayInfo.logicalWidth = LOGICAL_WIDTH; + displayInfo.logicalHeight = LOGICAL_HEIGHT; + } + + /** + * Set max bounds to be sandboxed to the app bounds, indicating the app is in + * size compat mode or letterbox. + */ + private static void setMaxBoundsSandboxedToMatchAppBounds(Resources resources, Rect appBounds) { + resources.getConfiguration().windowConfiguration.setMaxBounds(appBounds); + } + + /** + * Do not compare entire display info, since it is updated to match display the test is run on. + */ + private static void verifyDisplayInfo(DisplayInfo actual, DisplayInfo expected) { + assertThat(actual.displayId).isEqualTo(expected.displayId); + assertThat(actual.rotation).isEqualTo(expected.rotation); + assertThat(actual.logicalWidth).isEqualTo(LOGICAL_WIDTH); + assertThat(actual.logicalHeight).isEqualTo(LOGICAL_HEIGHT); + } + + private static void verifyRealSizeIsLandscape(Display display) { + Point size = new Point(); + display.getRealSize(size); + // Flip the width and height check since the device is rotated. + assertThat(size).isEqualTo(new Point(LOGICAL_HEIGHT, LOGICAL_WIDTH)); + } + + private static void verifyRealMetricsIsLandscape(Display display) { + DisplayMetrics metrics = new DisplayMetrics(); + display.getRealMetrics(metrics); + // Flip the width and height check since the device is rotated. + assertThat(metrics.widthPixels).isEqualTo(LOGICAL_HEIGHT); + assertThat(metrics.heightPixels).isEqualTo(LOGICAL_WIDTH); + } + + private static void verifyRealSizeIsPortrait(Display display) { + Point size = new Point(); + display.getRealSize(size); + assertThat(size).isEqualTo(new Point(LOGICAL_WIDTH, LOGICAL_HEIGHT)); + } + + private static void verifyRealMetricsIsPortrait(Display display) { + DisplayMetrics metrics = new DisplayMetrics(); + display.getRealMetrics(metrics); + assertThat(metrics.widthPixels).isEqualTo(LOGICAL_WIDTH); + assertThat(metrics.heightPixels).isEqualTo(LOGICAL_HEIGHT); + } + + private static void verifyRealSizeMatchesApp(Display display, Rect appBounds) { + Point size = new Point(); + display.getRealSize(size); + assertThat(size).isEqualTo(new Point(appBounds.width(), appBounds.height())); + } + + private static void verifyRealMetricsMatchesApp(Display display, Rect appBounds) { + DisplayMetrics metrics = new DisplayMetrics(); + display.getRealMetrics(metrics); + assertThat(metrics.widthPixels).isEqualTo(appBounds.width()); + assertThat(metrics.heightPixels).isEqualTo(appBounds.height()); + } + + private static FixedRotationAdjustments setOverrideFixedRotationAdjustments( + Resources resources, @Surface.Rotation int rotation) { + FixedRotationAdjustments fixedRotationAdjustments = + setFixedRotationAdjustments(resources, rotation); + resources.overrideDisplayAdjustments( + buildOverrideRotationAdjustments(fixedRotationAdjustments)); + return fixedRotationAdjustments; + } + + private static FixedRotationAdjustments setFixedRotationAdjustments(Resources resources, + @Surface.Rotation int rotation) { + final FixedRotationAdjustments fixedRotationAdjustments = + new FixedRotationAdjustments(rotation, APP_WIDTH, APP_HEIGHT, + DisplayCutout.NO_CUTOUT); + resources.getDisplayAdjustments().setFixedRotationAdjustments(fixedRotationAdjustments); + return fixedRotationAdjustments; + } + + private static Consumer buildOverrideRotationAdjustments( + FixedRotationAdjustments fixedRotationAdjustments) { + return consumedDisplayAdjustments + -> consumedDisplayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments); + } +} diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index b7bf8ab757991..43d56626ed1ac 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1219,6 +1219,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DragState.java" }, + "-681380736": { + "message": "Sandbox max bounds for uid %s to bounds %s due to letterboxing from mismatch with parent bounds? %s size compat mode %s", + "level": "DEBUG", + "group": "WM_DEBUG_CONFIGURATION", + "at": "com\/android\/server\/wm\/ActivityRecord.java" + }, "-677449371": { "message": "moveTaskToRootTask: moving task=%d to rootTaskId=%d toTop=%b", "level": "DEBUG", diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 5446a39fad862..2e94ca15c9d81 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; import static android.app.ActivityOptions.ANIM_CLIP_REVEAL; @@ -86,6 +87,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.isFixedOrientationLandscape; import static android.content.pm.ActivityInfo.isFixedOrientationPortrait; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.res.Configuration.EMPTY; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -205,6 +207,7 @@ import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainerChildProto.ACTIVITY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -278,6 +281,7 @@ import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.os.storage.StorageManager; +import android.permission.PermissionManager; import android.service.dreams.DreamActivity; import android.service.dreams.DreamManagerInternal; import android.service.voice.IVoiceInteractionSession; @@ -6955,6 +6959,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // layout traversals. mConfigurationSeq = Math.max(++mConfigurationSeq, 1); getResolvedOverrideConfiguration().seq = mConfigurationSeq; + + // Sandbox max bounds by setting it to the app bounds, if activity is letterboxed or in + // size compat mode. + if (providesMaxBounds()) { + if (DEBUG_CONFIGURATION) { + ProtoLog.d(WM_DEBUG_CONFIGURATION, "Sandbox max bounds for uid %s to bounds %s " + + "due to letterboxing from mismatch with parent bounds? %s size compat " + + "mode %s", getUid(), + resolvedConfig.windowConfiguration.getBounds(), !matchParentBounds(), + inSizeCompatMode()); + } + resolvedConfig.windowConfiguration + .setMaxBounds(resolvedConfig.windowConfiguration.getBounds()); + } } /** @@ -7296,6 +7314,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return super.getBounds(); } + @Override + public boolean providesMaxBounds() { + // System and SystemUI should always be able to access the physical display bounds, + // so do not provide it with the overridden maximum bounds. + // TODO(b/179179513) check WindowState#mOwnerCanAddInternalSystemWindow instead + if (getUid() == SYSTEM_UID || PermissionManager.checkPermission(INTERNAL_SYSTEM_WINDOW, + getPid(), info.applicationInfo.uid) == PERMISSION_GRANTED) { + return false; + } + // Max bounds should be sandboxed where an activity is letterboxed (activity bounds will be + // smaller than task bounds) or put in size compat mode. + return !matchParentBounds() || inSizeCompatMode(); + } + @VisibleForTesting @Override Rect getAnimationBounds(int appRootTaskClipMode) { diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 62a00802896f2..8fbe1775fd19f 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -367,8 +367,7 @@ public abstract class ConfigurationContainer { * Returns {@code true} if this {@link ConfigurationContainer} provides the maximum bounds to * its child {@link ConfigurationContainer}s. Returns {@code false}, otherwise. *

- * The maximum bounds is how large a window can be expanded. Currently only - * {@link DisplayContent} and {@link DisplayArea} effect this property. + * The maximum bounds is how large a window can be expanded. *

*/ protected boolean providesMaxBounds() { diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 2f1d7eb404ad9..e21e8bac38b94 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -40,6 +40,8 @@ import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.Task.ActivityState.STOPPED; import static com.android.server.wm.WindowContainer.POSITION_TOP; +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; @@ -117,18 +119,19 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testKeepBoundsWhenChangingFromFreeformToFullscreen() { removeGlobalMinSizeRestriction(); - // create freeform display and a freeform app + // Create landscape freeform display and a freeform app. DisplayContent display = new TestDisplayContent.Builder(mAtm, 2000, 1000) .setCanRotate(false) .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM).build(); setUpApp(display); - // Put app window into freeform and then make it a compat app. + // Put app window into portrait freeform and then make it a compat app. final Rect bounds = new Rect(100, 100, 400, 600); mTask.setBounds(bounds); - prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); assertEquals(bounds, mActivity.getBounds()); + // Activity is not yet in size compat mode; it is filling the freeform window. + assertMaxBoundsInheritDisplayAreaBounds(); // The activity should be able to accept negative x position [-150, 100 - 150, 600]. final int dx = bounds.left + bounds.width() / 2; @@ -137,7 +140,7 @@ public class SizeCompatTests extends WindowTestsBase { final int density = mActivity.getConfiguration().densityDpi; - // change display configuration to fullscreen + // Change display configuration to fullscreen. Configuration c = new Configuration(display.getRequestedOverrideConfiguration()); c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); display.onRequestedOverrideConfigurationChanged(c); @@ -147,6 +150,8 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(bounds.width(), mActivity.getBounds().width()); assertEquals(bounds.height(), mActivity.getBounds().height()); assertEquals(density, mActivity.getConfiguration().densityDpi); + // Size compat mode is sandboxed at the activity level. + assertActivityMaxBoundsSandboxed(); } @Test @@ -172,6 +177,12 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(appBounds.height(), appBounds.width() * aspectRatio, 0.5f /* delta */); // The decor height should be a part of the effective bounds. assertEquals(mActivity.getBounds().height(), appBounds.height() + notchHeight); + // Activity max bounds should be sandboxed; activity is letterboxed due to aspect ratio. + assertActivityMaxBoundsSandboxed(); + // Activity max bounds ignore notch, since an app can be shown past the notch (although app + // is currently limited by the notch). + assertThat(mActivity.getWindowConfiguration().getMaxBounds().height()) + .isEqualTo(displayBounds.height()); mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); assertFitted(); @@ -181,9 +192,17 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(appBounds.width(), appBounds.height() * aspectRatio, 0.5f /* delta */); // The notch is no longer on top. assertEquals(appBounds, mActivity.getBounds()); + // Activity max bounds are sandboxed. + assertActivityMaxBoundsSandboxed(); mActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); assertFitted(); + // Activity max bounds should be sandboxed; activity is letterboxed due to aspect ratio. + assertActivityMaxBoundsSandboxed(); + // Activity max bounds ignore notch, since an app can be shown past the notch (although app + // is currently limited by the notch). + assertThat(mActivity.getWindowConfiguration().getMaxBounds().height()) + .isEqualTo(displayBounds.height()); } @Test @@ -206,6 +225,9 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(originalBounds.width(), mActivity.getBounds().width()); assertEquals(originalBounds.height(), mActivity.getBounds().height()); assertEquals(originalDpi, mActivity.getConfiguration().densityDpi); + // Activity is sandboxed; it is in size compat mode since it is not resizable and has a + // max aspect ratio. + assertActivityMaxBoundsSandboxed(); assertScaled(); } @@ -213,11 +235,13 @@ public class SizeCompatTests extends WindowTestsBase { public void testFixedScreenBoundsWhenDisplaySizeChanged() { setUpDisplaySizeWithApp(1000, 2500); prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); + final DisplayContent display = mActivity.mDisplayContent; assertFitted(); + // Activity inherits bounds from TaskDisplayArea, since not sandboxed. + assertMaxBoundsInheritDisplayAreaBounds(); final Rect origBounds = new Rect(mActivity.getBounds()); final Rect currentBounds = mActivity.getWindowConfiguration().getBounds(); - final DisplayContent display = mActivity.mDisplayContent; // Change the size of current display. resizeDisplay(display, 1000, 2000); @@ -234,6 +258,8 @@ public class SizeCompatTests extends WindowTestsBase { // The position of configuration bounds should be the same as compat bounds. assertEquals(mActivity.getBounds().left, currentBounds.left); assertEquals(mActivity.getBounds().top, currentBounds.top); + // Activity is sandboxed to the offset size compat bounds. + assertActivityMaxBoundsSandboxed(); // Change display size to a different orientation resizeDisplay(display, 2000, 1000); @@ -242,6 +268,8 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(origBounds.height(), currentBounds.height()); assertEquals(ORIENTATION_LANDSCAPE, display.getConfiguration().orientation); assertEquals(Configuration.ORIENTATION_PORTRAIT, mActivity.getConfiguration().orientation); + // Activity is sandboxed to the offset size compat bounds. + assertActivityMaxBoundsSandboxed(); // The previous resize operation doesn't consider the rotation change after size changed. // These setups apply the requested orientation to rotation as real case that the top fixed @@ -261,6 +289,8 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(origBounds.height(), currentBounds.height()); assertEquals(offsetX, currentBounds.left); assertScaled(); + // Activity is sandboxed due to size compat mode. + assertActivityMaxBoundsSandboxed(); } @Test @@ -276,6 +306,8 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(bounds.width(), bounds.height() * maxAspect, 0.0001f /* delta */); // The position should be horizontal centered. assertEquals((displayWidth - bounds.width()) / 2, bounds.left); + // Activity max bounds should be sandboxed since it is letterboxed. + assertActivityMaxBoundsSandboxed(); mActivity.mDisplayContent.setImeLayeringTarget(addWindowToActivity(mActivity)); // Make sure IME cannot attach to the app, otherwise IME window will also be shifted. @@ -287,6 +319,8 @@ public class SizeCompatTests extends WindowTestsBase { // It should keep non-attachable because the resolved bounds will be computed according to // the aspect ratio that won't match its parent bounds. assertFalse(mActivity.mDisplayContent.isImeAttachedToApp()); + // Activity max bounds should be sandboxed since it is letterboxed. + assertActivityMaxBoundsSandboxed(); } @Test @@ -312,14 +346,13 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testMoveToDifferentOrientDisplay() { + public void testMoveToDifferentOrientationDisplay() { setUpDisplaySizeWithApp(1000, 2500); prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); assertFitted(); - final Rect configBounds = mActivity.getWindowConfiguration().getBounds(); - final int origWidth = configBounds.width(); - final int origHeight = configBounds.height(); + final Rect currentBounds = mActivity.getWindowConfiguration().getBounds(); + final Rect originalBounds = new Rect(mActivity.getWindowConfiguration().getBounds()); final int notchHeight = 100; final DisplayContent newDisplay = new TestDisplayContent.Builder(mAtm, 2000, 1000) @@ -328,37 +361,45 @@ public class SizeCompatTests extends WindowTestsBase { // Move the non-resizable activity to the new display. mTask.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */); // The configuration bounds [820, 0 - 1820, 2500] should keep the same. - assertEquals(origWidth, configBounds.width()); - assertEquals(origHeight, configBounds.height()); + assertEquals(originalBounds.width(), currentBounds.width()); + assertEquals(originalBounds.height(), currentBounds.height()); assertScaled(); + // Activity max bounds are sandboxed due to size compat mode on the new display. + assertActivityMaxBoundsSandboxed(); final Rect newDisplayBounds = newDisplay.getWindowConfiguration().getBounds(); // The scaled bounds should exclude notch area (1000 - 100 == 360 * 2500 / 1000 = 900). assertEquals(newDisplayBounds.height() - notchHeight, - (int) ((float) mActivity.getBounds().width() * origHeight / origWidth)); + (int) ((float) mActivity.getBounds().width() * originalBounds.height() + / originalBounds.width())); // Recompute the natural configuration in the new display. mActivity.clearSizeCompatMode(); mActivity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */); // Because the display cannot rotate, the portrait activity will fit the short side of // display with keeping portrait bounds [200, 0 - 700, 1000] in center. - assertEquals(newDisplayBounds.height(), configBounds.height()); - assertEquals(configBounds.height() * newDisplayBounds.height() / newDisplayBounds.width(), - configBounds.width()); + assertEquals(newDisplayBounds.height(), currentBounds.height()); + assertEquals(currentBounds.height() * newDisplayBounds.height() / newDisplayBounds.width(), + currentBounds.width()); assertFitted(); // The appBounds should be [200, 100 - 700, 1000]. final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds(); - assertEquals(configBounds.width(), appBounds.width()); - assertEquals(configBounds.height() - notchHeight, appBounds.height()); + assertEquals(currentBounds.width(), appBounds.width()); + assertEquals(currentBounds.height() - notchHeight, appBounds.height()); + // Activity max bounds are sandboxed due to letterboxing from orientation mismatch with + // display. + assertActivityMaxBoundsSandboxed(); } @Test - public void testFixedOrientRotateCutoutDisplay() { + public void testFixedOrientationRotateCutoutDisplay() { // Create a display with a notch/cutout final int notchHeight = 60; - setUpApp(new TestDisplayContent.Builder(mAtm, 1000, 2500) + final int width = 1000; + setUpApp(new TestDisplayContent.Builder(mAtm, width, 2500) .setNotch(notchHeight).build()); - // Bounds=[0, 0 - 1000, 1460], AppBounds=[0, 60 - 1000, 1460]. + // Bounds=[0, 0 - 1000, 1400], AppBounds=[0, 60 - 1000, 1460]. + final float maxAspect = 1.4f; prepareUnresizable(mActivity, 1.4f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); final Rect currentBounds = mActivity.getWindowConfiguration().getBounds(); @@ -366,6 +407,11 @@ public class SizeCompatTests extends WindowTestsBase { final Rect origBounds = new Rect(currentBounds); final Rect origAppBounds = new Rect(appBounds); + // Activity is sandboxed, and bounds include the area consumed by the notch. + assertActivityMaxBoundsSandboxed(); + assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds().height()) + .isEqualTo(Math.round(width * maxAspect) + notchHeight); + // Although the activity is fixed orientation, force rotate the display. rotateDisplay(mActivity.mDisplayContent, ROTATION_270); assertEquals(ROTATION_270, mTask.getWindowConfiguration().getRotation()); @@ -381,10 +427,13 @@ public class SizeCompatTests extends WindowTestsBase { // The position in configuration should be global coordinates. assertEquals(mActivity.getBounds().left, currentBounds.left); assertEquals(mActivity.getBounds().top, currentBounds.top); + + // Activity max bounds are sandboxed due to size compat mode. + assertActivityMaxBoundsSandboxed(); } @Test - public void testFixedAspOrientChangeOrient() { + public void testFixedAspectRatioOrientationChangeOrientation() { setUpDisplaySizeWithApp(1000, 2500); final float maxAspect = 1.4f; @@ -396,6 +445,8 @@ public class SizeCompatTests extends WindowTestsBase { final Rect originalAppBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds()); assertEquals((int) (originalBounds.width() * maxAspect), originalBounds.height()); + // Activity is sandboxed due to fixed aspect ratio. + assertActivityMaxBoundsSandboxed(); // Change the fixed orientation. mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE); @@ -407,6 +458,8 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.getWindowConfiguration().getAppBounds().height()); assertEquals(originalAppBounds.height(), mActivity.getWindowConfiguration().getAppBounds().width()); + // Activity is sandboxed due to fixed aspect ratio. + assertActivityMaxBoundsSandboxed(); } @Test @@ -455,6 +508,8 @@ public class SizeCompatTests extends WindowTestsBase { // restarted and the override configuration won't be cleared. verify(mActivity, never()).restartProcessIfVisible(); assertScaled(); + // Activity max bounds are sandboxed due to size compat mode, even if is not visible. + assertActivityMaxBoundsSandboxed(); // Change display density display.mBaseDisplayDensity = (int) (0.7f * display.mBaseDisplayDensity); @@ -568,12 +623,16 @@ public class SizeCompatTests extends WindowTestsBase { // in multi-window mode. mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); assertFalse(activity.shouldCreateCompatDisplayInsets()); + // Activity should not be sandboxed. + assertMaxBoundsInheritDisplayAreaBounds(); // The non-resizable activity should not be size compat because the display support // changing windowing mode from fullscreen to freeform. mTask.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); assertFalse(activity.shouldCreateCompatDisplayInsets()); + // Activity should not be sandboxed. + assertMaxBoundsInheritDisplayAreaBounds(); } @Test @@ -637,6 +696,9 @@ public class SizeCompatTests extends WindowTestsBase { // be transparent. assertFalse(displayPolicy.isFullyTransparentAllowed(w, TYPE_STATUS_BAR)); + // Activity is sandboxed. + assertActivityMaxBoundsSandboxed(); + // Make the activity fill the display. prepareUnresizable(mActivity, 10 /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE); w.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; @@ -646,6 +708,7 @@ public class SizeCompatTests extends WindowTestsBase { // The letterbox should only cover the notch area, so status bar can be transparent. assertEquals(new Rect(notchHeight, 0, 0, 0), mActivity.getLetterboxInsets()); assertTrue(displayPolicy.isFullyTransparentAllowed(w, TYPE_STATUS_BAR)); + assertActivityMaxBoundsSandboxed(); } @Test @@ -668,6 +731,7 @@ public class SizeCompatTests extends WindowTestsBase { // App should launch in fixed orientation letterbox. assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); + assertActivityMaxBoundsSandboxed(); // Activity bounds should be 700x1400 with the ratio as the display. assertEquals(displayBounds.height(), activityBounds.height()); @@ -789,6 +853,7 @@ public class SizeCompatTests extends WindowTestsBase { assertScaled(); assertEquals(activityBounds.width(), newActivityBounds.width()); assertEquals(activityBounds.height(), newActivityBounds.height()); + assertActivityMaxBoundsSandboxed(); } @Test @@ -800,29 +865,29 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app without max aspect. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); - Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds()); - Rect activityBounds = new Rect(mActivity.getBounds()); - // App should launch in fullscreen. assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); - assertEquals(displayBounds, activityBounds); + // Activity inherits max bounds from TaskDisplayArea. + assertMaxBoundsInheritDisplayAreaBounds(); // Rotate display to landscape. rotateDisplay(mActivity.mDisplayContent, ROTATION_90); - displayBounds = new Rect(mActivity.mDisplayContent.getBounds()); - activityBounds = new Rect(mActivity.getBounds()); - assertTrue(displayBounds.width() > displayBounds.height()); + final Rect rotatedDisplayBounds = new Rect(mActivity.mDisplayContent.getBounds()); + final Rect rotatedActivityBounds = new Rect(mActivity.getBounds()); + assertTrue(rotatedDisplayBounds.width() > rotatedDisplayBounds.height()); // App should be in size compat. assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); + assertThat(mActivity.inSizeCompatMode()).isTrue(); + assertActivityMaxBoundsSandboxed(); // App bounds should be 700x1400 with the ratio as the display. - assertEquals(displayBounds.height(), activityBounds.height()); - assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(), - activityBounds.width()); + assertEquals(rotatedDisplayBounds.height(), rotatedActivityBounds.height()); + assertEquals(rotatedDisplayBounds.height() * rotatedDisplayBounds.height() + / rotatedDisplayBounds.width(), rotatedActivityBounds.width()); } @Test @@ -859,6 +924,7 @@ public class SizeCompatTests extends WindowTestsBase { // has 700x1400 bounds with the ratio as the display. assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(newActivity.inSizeCompatMode()); + assertActivityMaxBoundsSandboxed(); assertEquals(taskBounds, displayBounds); assertEquals(displayBounds.height(), newActivityBounds.height()); assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(), @@ -899,6 +965,11 @@ public class SizeCompatTests extends WindowTestsBase { // Task bounds should fill parent bounds. assertEquals(displayBounds, taskBounds); + // Prior and new activity max bounds are sandboxed due to letterbox. + assertThat(newActivity.getConfiguration().windowConfiguration.getMaxBounds()) + .isEqualTo(newActivityBounds); + assertActivityMaxBoundsSandboxed(); + // Activity bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio. assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(newActivity.inSizeCompatMode()); @@ -927,6 +998,9 @@ public class SizeCompatTests extends WindowTestsBase { // App should be in size compat. assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); + assertThat(mActivity.inSizeCompatMode()).isTrue(); + // Activity max bounds are sandboxed due to size compat mode. + assertActivityMaxBoundsSandboxed(); final Rect activityBounds = new Rect(mActivity.getBounds()); mTask.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */); @@ -936,6 +1010,8 @@ public class SizeCompatTests extends WindowTestsBase { assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); assertEquals(activityBounds, mActivity.getBounds()); + // Activity max bounds are sandboxed due to size compat. + assertActivityMaxBoundsSandboxed(); } @Test @@ -951,6 +1027,7 @@ public class SizeCompatTests extends WindowTestsBase { // In fixed orientation letterbox assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); + assertActivityMaxBoundsSandboxed(); // Rotate display to portrait. rotateDisplay(display, ROTATION_90); @@ -958,13 +1035,15 @@ public class SizeCompatTests extends WindowTestsBase { // App should be in size compat. assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); + assertActivityMaxBoundsSandboxed(); // Rotate display to landscape. rotateDisplay(display, ROTATION_180); - // In Task letterbox + // In activity letterbox assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); + assertActivityMaxBoundsSandboxed(); } @Test @@ -982,20 +1061,23 @@ public class SizeCompatTests extends WindowTestsBase { // In fixed orientation letterbox assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); + assertActivityMaxBoundsSandboxed(); - // Rotate display to portrait. + // Rotate display to landscape. rotateDisplay(display, ROTATION_90); // App should be in size compat. assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); + assertActivityMaxBoundsSandboxed(); - // Rotate display to landscape. + // Rotate display to portrait. rotateDisplay(display, ROTATION_180); // In fixed orientation letterbox assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); + assertActivityMaxBoundsSandboxed(); } @Test @@ -1012,12 +1094,18 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(ORIENTATION_LANDSCAPE, display.getConfiguration().orientation); assertEquals(2800, displayBounds.width()); assertEquals(1400, displayBounds.height()); - taskDisplayArea.setBounds(0, 0, 2400, 1000); + Rect displayAreaBounds = new Rect(0, 0, 2400, 1000); + taskDisplayArea.setBounds(displayAreaBounds); final Rect activityBounds = new Rect(mActivity.getBounds()); assertFalse(mActivity.inSizeCompatMode()); assertEquals(2400, activityBounds.width()); assertEquals(1000, activityBounds.height()); + // Task and activity maximum bounds inherit from TaskDisplayArea bounds. + assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds()) + .isEqualTo(displayAreaBounds); + assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds()) + .isEqualTo(displayAreaBounds); } @Test @@ -1042,6 +1130,7 @@ public class SizeCompatTests extends WindowTestsBase { assertScaled(); assertEquals(originalBounds, mActivity.getConfiguration().windowConfiguration.getBounds()); + assertActivityMaxBoundsSandboxed(); // Recompute the natural configuration of the non-resizable activity and the split screen. mActivity.clearSizeCompatMode(); @@ -1053,12 +1142,13 @@ public class SizeCompatTests extends WindowTestsBase { addWindowToActivity(mActivity); mActivity.mRootWindowContainer.performSurfacePlacement(); - // Split screen is also in portrait [1000,1400], so activty should be in fixed orientation + // Split screen is also in portrait [1000,1400], so activity should be in fixed orientation // letterbox. assertEquals(ORIENTATION_PORTRAIT, mTask.getConfiguration().orientation); assertEquals(ORIENTATION_LANDSCAPE, mActivity.getConfiguration().orientation); assertFitted(); assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); + assertActivityMaxBoundsSandboxed(); // Letterbox should fill the gap between the split screen and the letterboxed activity. final Rect primarySplitBounds = new Rect(organizer.mPrimary.getBounds()); @@ -1165,6 +1255,22 @@ public class SizeCompatTests extends WindowTestsBase { assertFalse(mActivity.hasSizeCompatBounds()); } + /** Asserts the activity max bounds inherit from the TaskDisplayArea. */ + private void assertMaxBoundsInheritDisplayAreaBounds() { + assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds()) + .isEqualTo(mTask.getDisplayArea().getBounds()); + } + + /** + * Asserts activity-level letterbox or size compat mode size compat mode, so activity max + * bounds are sandboxed. + */ + private void assertActivityMaxBoundsSandboxed() { + // Activity max bounds are sandboxed due to size compat mode. + assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds()) + .isEqualTo(mActivity.getWindowConfiguration().getBounds()); + } + static Configuration rotateDisplay(DisplayContent display, int rotation) { final Configuration c = new Configuration(); display.getDisplayRotation().setRotation(rotation); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index ae85ceb729587..cac69657f5ccc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -136,6 +136,7 @@ class TestDisplayContent extends DisplayContent { final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, mInfo, DEFAULT_DISPLAY_ADJUSTMENTS); final TestDisplayContent newDisplay = createInternal(display); + // disable the normal system decorations final DisplayPolicy displayPolicy = newDisplay.getDisplayPolicy(); spyOn(displayPolicy); From 6574e37f7c40aada1a4d823b51fa4772ba368114 Mon Sep 17 00:00:00 2001 From: Naomi Musgrave Date: Mon, 1 Mar 2021 18:38:47 +0000 Subject: [PATCH 2/2] Temporarily exclude Launcher from sandboxing; to be reverted once bug is addressed. Bug: 181219241 Test: Manual Change-Id: I468104a853908c5649359084fa9d683e7f1ce532 --- core/java/android/view/Display.java | 42 ++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 229e74b262b8b..b98ef1410ebf4 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -24,9 +24,11 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.TestApi; +import android.app.ActivityThread; import android.app.KeyguardManager; import android.app.WindowConfiguration; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -45,11 +47,14 @@ import android.os.SystemClock; import android.util.DisplayMetrics; import android.util.Log; +import com.android.internal.R; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; /** * Provides information about the size and density of a logical display. @@ -112,6 +117,12 @@ public final class Display { */ private boolean mMayAdjustByFixedRotation; + /** + * Cache if the application is the recents component. + * TODO(b/179308296) Remove once Launcher addresses issue + */ + private Optional mIsRecentsComponent = Optional.empty(); + /** * The default Display id, which is the id of the primary display assuming there is one. */ @@ -1383,7 +1394,36 @@ public final class Display { return false; } final Configuration config = mResources.getConfiguration(); - return config != null && !config.windowConfiguration.getMaxBounds().isEmpty(); + // TODO(b/179308296) Temporarily exclude Launcher from being given max bounds, by checking + // if the caller is the recents component. + return config != null && !config.windowConfiguration.getMaxBounds().isEmpty() + && !isRecentsComponent(); + } + + /** + * Returns {@code true} when the calling package is the recents component. + * TODO(b/179308296) Remove once Launcher addresses issue + */ + boolean isRecentsComponent() { + if (mIsRecentsComponent.isPresent()) { + return mIsRecentsComponent.get(); + } + if (mResources == null) { + return false; + } + try { + String recentsComponent = mResources.getString(R.string.config_recentsComponentName); + if (recentsComponent == null) { + return false; + } + String recentsPackage = ComponentName.unflattenFromString(recentsComponent) + .getPackageName(); + mIsRecentsComponent = Optional.of(recentsPackage != null + && recentsPackage.equals(ActivityThread.currentPackageName())); + return mIsRecentsComponent.get(); + } catch (Resources.NotFoundException e) { + return false; + } } /**