| /* |
| * Copyright (C) 2016 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.server.wm; |
| |
| import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; |
| import static android.server.wm.ComponentNameUtils.getWindowName; |
| import static android.server.wm.WindowManagerState.dpToPx; |
| import static android.server.wm.app.Components.BOTTOM_LEFT_LAYOUT_ACTIVITY; |
| import static android.server.wm.app.Components.BOTTOM_RIGHT_LAYOUT_ACTIVITY; |
| import static android.server.wm.app.Components.TEST_ACTIVITY; |
| import static android.server.wm.app.Components.TOP_LEFT_LAYOUT_ACTIVITY; |
| import static android.server.wm.app.Components.TOP_RIGHT_LAYOUT_ACTIVITY; |
| import static android.view.Display.DEFAULT_DISPLAY; |
| import static android.view.WindowInsets.Type.captionBar; |
| import static android.view.WindowInsets.Type.systemBars; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assume.assumeTrue; |
| |
| import android.content.ComponentName; |
| import android.graphics.Rect; |
| import android.platform.test.annotations.Presubmit; |
| import android.server.wm.WindowManagerState.WindowState; |
| import android.util.DisplayMetrics; |
| import android.view.DisplayCutout; |
| import android.view.WindowMetrics; |
| |
| import org.junit.Test; |
| |
| import java.util.List; |
| |
| /** |
| * Build/Install/Run: |
| * atest CtsWindowManagerDeviceTestCases:ManifestLayoutTests |
| */ |
| @Presubmit |
| public class ManifestLayoutTests extends ActivityManagerTestBase { |
| |
| // Test parameters |
| private static final int DEFAULT_WIDTH_DP = 240; |
| private static final int DEFAULT_HEIGHT_DP = 160; |
| private static final float DEFAULT_WIDTH_FRACTION = 0.50f; |
| private static final float DEFAULT_HEIGHT_FRACTION = 0.70f; |
| private static final int MIN_WIDTH_DP = 100; |
| private static final int MIN_HEIGHT_DP = 80; |
| |
| private static final int GRAVITY_VER_CENTER = 0x01; |
| private static final int GRAVITY_VER_TOP = 0x02; |
| private static final int GRAVITY_VER_BOTTOM = 0x04; |
| private static final int GRAVITY_HOR_CENTER = 0x10; |
| private static final int GRAVITY_HOR_LEFT = 0x20; |
| private static final int GRAVITY_HOR_RIGHT = 0x40; |
| |
| private WindowManagerState.DisplayContent mDisplay; |
| private WindowState mWindowState; |
| |
| @Test |
| public void testGravityAndDefaultSizeTopLeft() throws Exception { |
| testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_LEFT, false /*fraction*/); |
| } |
| |
| @Test |
| public void testGravityAndDefaultSizeTopRight() throws Exception { |
| testLayout(GRAVITY_VER_TOP, GRAVITY_HOR_RIGHT, true /*fraction*/); |
| } |
| |
| @Test |
| public void testGravityAndDefaultSizeBottomLeft() throws Exception { |
| testLayout(GRAVITY_VER_BOTTOM, GRAVITY_HOR_LEFT, true /*fraction*/); |
| } |
| |
| @Test |
| public void testGravityAndDefaultSizeBottomRight() throws Exception { |
| testLayout(GRAVITY_VER_BOTTOM, GRAVITY_HOR_RIGHT, false /*fraction*/); |
| } |
| |
| @Test |
| public void testMinimalSizeFreeform() throws Exception { |
| assumeTrue("Skipping test: no freeform support", supportsFreeform()); |
| |
| testMinimalSize(true /* freeform */); |
| } |
| |
| @Test |
| @Presubmit |
| public void testMinimalSizeDocked() throws Exception { |
| assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow()); |
| |
| testMinimalSize(false /* freeform */); |
| } |
| |
| private void testMinimalSize(boolean freeform) throws Exception { |
| // Issue command to resize to <0,0,1,1>. We expect the size to be floored at |
| // MIN_WIDTH_DPxMIN_HEIGHT_DP. |
| if (freeform) { |
| launchActivity(BOTTOM_RIGHT_LAYOUT_ACTIVITY, WINDOWING_MODE_FREEFORM); |
| resizeActivityTask(BOTTOM_RIGHT_LAYOUT_ACTIVITY, 0, 0, 1, 1); |
| } else { // stackId == DOCKED_STACK_ID |
| launchActivitiesInSplitScreen( |
| getLaunchActivityBuilder().setTargetActivity(BOTTOM_RIGHT_LAYOUT_ACTIVITY), |
| getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)); |
| mTaskOrganizer.setRootPrimaryTaskBounds(new Rect(0, 0, 1, 1)); |
| } |
| getDisplayAndWindowState(BOTTOM_RIGHT_LAYOUT_ACTIVITY, false); |
| |
| // Use default density because ActivityInfo.WindowLayout is initialized by that. |
| final int minWidth = dpToPx(MIN_WIDTH_DP, DisplayMetrics.DENSITY_DEVICE_STABLE); |
| final int minHeight = dpToPx(MIN_HEIGHT_DP, DisplayMetrics.DENSITY_DEVICE_STABLE); |
| final Rect parentFrame = mWindowState.getParentFrame(); |
| final int cutoutSize = getCutoutSizeByHorGravity(GRAVITY_HOR_LEFT); |
| |
| assertEquals("Min width is incorrect", minWidth, parentFrame.width() + cutoutSize); |
| assertEquals("Min height is incorrect", minHeight, parentFrame.height()); |
| } |
| |
| private void testLayout( |
| int vGravity, int hGravity, boolean fraction) throws Exception { |
| assumeTrue("Skipping test: no freeform support", supportsFreeform()); |
| |
| final ComponentName activityName; |
| if (vGravity == GRAVITY_VER_TOP) { |
| activityName = (hGravity == GRAVITY_HOR_LEFT) ? TOP_LEFT_LAYOUT_ACTIVITY |
| : TOP_RIGHT_LAYOUT_ACTIVITY; |
| } else { |
| activityName = (hGravity == GRAVITY_HOR_LEFT) ? BOTTOM_LEFT_LAYOUT_ACTIVITY |
| : BOTTOM_RIGHT_LAYOUT_ACTIVITY; |
| } |
| |
| // Launch in freeform stack |
| launchActivity(activityName, WINDOWING_MODE_FREEFORM); |
| |
| getDisplayAndWindowState(activityName, true); |
| |
| final Rect parentFrame = mWindowState.getParentFrame(); |
| final WindowMetrics windowMetrics = mWm.getMaximumWindowMetrics(); |
| final Rect stableBounds = new Rect(windowMetrics.getBounds()); |
| stableBounds.inset(windowMetrics.getWindowInsets().getInsetsIgnoringVisibility( |
| systemBars() & ~captionBar())); |
| final int expectedWidthPx, expectedHeightPx; |
| // Evaluate the expected window size in px. If we're using fraction dimensions, |
| // calculate the size based on the app rect size. Otherwise, convert the expected |
| // size in dp to px. |
| if (fraction) { |
| expectedWidthPx = (int) (stableBounds.width() * DEFAULT_WIDTH_FRACTION); |
| expectedHeightPx = (int) (stableBounds.height() * DEFAULT_HEIGHT_FRACTION); |
| } else { |
| final int densityDpi = DisplayMetrics.DENSITY_DEVICE_STABLE; |
| expectedWidthPx = dpToPx(DEFAULT_WIDTH_DP, densityDpi); |
| expectedHeightPx = dpToPx(DEFAULT_HEIGHT_DP, densityDpi); |
| } |
| |
| verifyFrameSizeAndPosition(vGravity, hGravity, expectedWidthPx, expectedHeightPx, |
| parentFrame, stableBounds); |
| } |
| |
| private void getDisplayAndWindowState(ComponentName activityName, boolean checkFocus) |
| throws Exception { |
| final String windowName = getWindowName(activityName); |
| |
| mWmState.computeState(activityName); |
| |
| if (checkFocus) { |
| mWmState.assertFocusedWindow("Test window must be the front window.", windowName); |
| } else { |
| mWmState.assertVisibility(activityName, true); |
| } |
| |
| final List<WindowState> windowList = |
| mWmState.getMatchingVisibleWindowState(windowName); |
| |
| assertEquals("Should have exactly one window state for the activity.", |
| 1, windowList.size()); |
| |
| mWindowState = windowList.get(0); |
| assertNotNull("Should have a valid window", mWindowState); |
| |
| mDisplay = mWmState.getDisplay(mWindowState.getDisplayId()); |
| assertNotNull("Should be on a display", mDisplay); |
| } |
| |
| private void verifyFrameSizeAndPosition( |
| int vGravity, int hGravity, int expectedWidthPx, int expectedHeightPx, |
| Rect parentFrame, Rect stableBounds) { |
| final int cutoutSize = getCutoutSizeByHorGravity(hGravity); |
| assertEquals("Width is incorrect", |
| expectedWidthPx, parentFrame.width() + cutoutSize); |
| assertEquals("Height is incorrect", expectedHeightPx, parentFrame.height()); |
| |
| if (vGravity == GRAVITY_VER_TOP) { |
| assertEquals("Should be on the top", stableBounds.top, parentFrame.top); |
| } else if (vGravity == GRAVITY_VER_BOTTOM) { |
| assertEquals("Should be on the bottom", stableBounds.bottom, parentFrame.bottom); |
| } |
| |
| if (hGravity == GRAVITY_HOR_LEFT) { |
| assertEquals("Should be on the left", |
| stableBounds.left, parentFrame.left - cutoutSize); |
| } else if (hGravity == GRAVITY_HOR_RIGHT){ |
| assertEquals("Should be on the right", |
| stableBounds.right, parentFrame.right + cutoutSize); |
| } |
| } |
| |
| private int getCutoutSizeByHorGravity(int hGravity) { |
| DisplayCutout cutout = mDm.getDisplay(DEFAULT_DISPLAY).getCutout(); |
| if (cutout == null) { |
| return 0; |
| } |
| |
| // When the layoutInDisplayCutoutMode is default, the status bar & navigation bar already |
| // take top and bottom cutout into account. |
| // Here we only need to account for left & right cutout areas. |
| if (hGravity == GRAVITY_HOR_LEFT) { |
| return cutout.getSafeInsetLeft(); |
| } else if (hGravity == GRAVITY_HOR_RIGHT) { |
| return cutout.getSafeInsetRight(); |
| } else { |
| return 0; |
| } |
| } |
| } |