/* * 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.server.wm.ComponentNameUtils.getWindowName; import static android.server.wm.DialogFrameTestActivity.DIALOG_WINDOW_NAME; import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_POSITION_MATCH_PARENT; import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_POSITION_MATCH_PARENT_NO_LIMITS; import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_SIZE; import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_SIZE_BOTTOM_RIGHT_GRAVITY; import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_SIZE_TOP_LEFT_GRAVITY; import static android.server.wm.DialogFrameTestActivity.TEST_MATCH_PARENT; import static android.server.wm.DialogFrameTestActivity.TEST_NO_FOCUS; import static android.server.wm.DialogFrameTestActivity.TEST_OVER_SIZED_DIMENSIONS; import static android.server.wm.DialogFrameTestActivity.TEST_OVER_SIZED_DIMENSIONS_NO_LIMITS; import static android.server.wm.DialogFrameTestActivity.TEST_WITH_MARGINS; import static android.view.WindowInsets.Type.captionBar; import static androidx.test.InstrumentationRegistry.getInstrumentation; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThan; import static org.junit.Assert.assertEquals; import android.content.ComponentName; import android.graphics.Insets; import android.graphics.Rect; import android.platform.test.annotations.AppModeFull; import android.platform.test.annotations.Presubmit; import android.server.wm.WindowManagerState.WindowState; import android.view.WindowInsets; import androidx.test.rule.ActivityTestRule; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import java.util.List; /** * Build/Install/Run: * atest CtsWindowManagerDeviceTestCases:DialogFrameTests * * TODO: Consolidate this class with {@link ParentChildTestBase}. */ @AppModeFull(reason = "Requires android.permission.MANAGE_ACTIVITY_TASKS") @Presubmit public class DialogFrameTests extends ParentChildTestBase { private static final ComponentName DIALOG_FRAME_TEST_ACTIVITY = new ComponentName( getInstrumentation().getContext(), DialogFrameTestActivity.class); private Insets mContentInsets; @Rule public final ActivityTestRule mDialogTestActivity = new ActivityTestRule<>(DialogFrameTestActivity.class, false /* initialTOuchMode */, false /* launchActivity */); @Override ComponentName activityName() { return DIALOG_FRAME_TEST_ACTIVITY; } @Override ActivityTestRule activityRule() { return mDialogTestActivity; } private WindowState getSingleWindow(final String windowName) { final List windowList = mWmState.getMatchingVisibleWindowState(windowName); assertThat(windowList.size(), greaterThan(0)); return windowList.get(0); } @Override void doSingleTest(ParentChildTest t) throws Exception { mWmState.computeState(WaitForValidActivityState.forWindow(DIALOG_WINDOW_NAME)); WindowState dialog = getSingleWindow(DIALOG_WINDOW_NAME); WindowState parent = getSingleWindow(getWindowName(activityName())); t.doTest(parent, dialog); } // With Width and Height as MATCH_PARENT we should fill // the same content frame as the main activity window @Test public void testMatchParentDialog() throws Exception { doParentChildTest(TEST_MATCH_PARENT, (parent, dialog) -> { ; assertEquals(getParentFrameWithInsets(parent), dialog.getFrame()); }); } private static final int explicitDimension = 200; // The default gravity for dialogs should center them. @Test public void testExplicitSizeDefaultGravity() throws Exception { doParentChildTest(TEST_EXPLICIT_SIZE, (parent, dialog) -> { Rect parentFrame = getParentFrameWithInsets(parent); Rect expectedFrame = new Rect( parentFrame.left + (parentFrame.width() - explicitDimension) / 2, parentFrame.top + (parentFrame.height() - explicitDimension) / 2, parentFrame.left + (parentFrame.width() + explicitDimension) / 2, parentFrame.top + (parentFrame.height() + explicitDimension) / 2); assertEquals(expectedFrame, dialog.getFrame()); }); } @Test public void testExplicitSizeTopLeftGravity() throws Exception { doParentChildTest(TEST_EXPLICIT_SIZE_TOP_LEFT_GRAVITY, (parent, dialog) -> { Rect parentFrame = getParentFrameWithInsets(parent); Rect expectedFrame = new Rect( parentFrame.left, parentFrame.top, parentFrame.left + explicitDimension, parentFrame.top + explicitDimension); assertEquals(expectedFrame, dialog.getFrame()); }); } @Test public void testExplicitSizeBottomRightGravity() throws Exception { doParentChildTest(TEST_EXPLICIT_SIZE_BOTTOM_RIGHT_GRAVITY, (parent, dialog) -> { Rect parentFrame = getParentFrameWithInsets(parent); Rect expectedFrame = new Rect( parentFrame.left + parentFrame.width() - explicitDimension, parentFrame.top + parentFrame.height() - explicitDimension, parentFrame.left + parentFrame.width(), parentFrame.top + parentFrame.height()); assertEquals(expectedFrame, dialog.getFrame()); }); } // TODO(b/30127373): Commented out for now because it doesn't work. We end up insetting the // decor on the bottom. I think this is a bug probably in the default dialog flags: @Ignore @Test public void testOversizedDimensions() throws Exception { doParentChildTest(TEST_OVER_SIZED_DIMENSIONS, (parent, dialog) -> // With the default flags oversize should result in clipping to // parent frame. assertEquals(getParentFrameWithInsets(parent), dialog.getFrame()) ); } // TODO(b/63993863) : Disabled pending public API to fetch maximum surface size. static final int oversizedDimension = 5000; // With FLAG_LAYOUT_NO_LIMITS we should get the size we request, even if its much larger than // the screen. @Ignore @Test public void testOversizedDimensionsNoLimits() throws Exception { // TODO(b/36890978): We only run this in fullscreen because of the // unclear status of NO_LIMITS for non-child surfaces in MW modes doFullscreenTest(TEST_OVER_SIZED_DIMENSIONS_NO_LIMITS, (parent, dialog) -> { Rect parentFrame = getParentFrameWithInsets(parent); Rect expectedFrame = new Rect(parentFrame.left, parentFrame.top, parentFrame.left + oversizedDimension, parentFrame.top + oversizedDimension); assertEquals(expectedFrame, dialog.getFrame()); }); } // If we request the MATCH_PARENT and a non-zero position, we wouldn't be // able to fit all of our content, so we should be adjusted to just fit the // content frame. @Test public void testExplicitPositionMatchParent() throws Exception { doParentChildTest(TEST_EXPLICIT_POSITION_MATCH_PARENT, (parent, dialog) -> assertEquals(getParentFrameWithInsets(parent), dialog.getFrame()) ); } // Unless we pass NO_LIMITS in which case our requested position should // be honored. @Test public void testExplicitPositionMatchParentNoLimits() throws Exception { final int explicitPosition = 100; doParentChildTest(TEST_EXPLICIT_POSITION_MATCH_PARENT_NO_LIMITS, (parent, dialog) -> { Rect parentFrame = getParentFrameWithInsets(parent); Rect expectedFrame = new Rect(parentFrame); expectedFrame.offset(explicitPosition, explicitPosition); assertEquals(expectedFrame, dialog.getFrame()); }); } // We run the two focus tests fullscreen only because switching to the // docked stack will strip away focus from the task anyway. @Test public void testDialogReceivesFocus() throws Exception { doFullscreenTest(TEST_MATCH_PARENT, (parent, dialog) -> assertEquals(dialog.getName(), mWmState.getFocusedWindow()) ); } @Test public void testNoFocusDialog() throws Exception { doFullscreenTest(TEST_NO_FOCUS, (parent, dialog) -> assertEquals(parent.getName(), mWmState.getFocusedWindow()) ); } @Test public void testMarginsArePercentagesOfContentFrame() throws Exception { float horizontalMargin = .10f; float verticalMargin = .15f; doParentChildTest(TEST_WITH_MARGINS, (parent, dialog) -> { Rect frame = getParentFrameWithInsets(parent); Rect expectedFrame = new Rect( (int) (horizontalMargin * frame.width() + frame.left), (int) (verticalMargin * frame.height() + frame.top), (int) (horizontalMargin * frame.width() + frame.left) + explicitDimension, (int) (verticalMargin * frame.height() + frame.top) + explicitDimension); assertEquals(expectedFrame, dialog.getFrame()); }); } @Test public void testDialogPlacedAboveParent() throws Exception { final WindowManagerState wmState = mWmState; doParentChildTest(TEST_MATCH_PARENT, (parent, dialog) -> // Not only should the dialog be higher, but it should be leave multiple layers of // space in between for DimLayers, etc... assertThat(wmState.getZOrder(dialog), greaterThan(wmState.getZOrder(parent))) ); } private Rect getParentFrameWithInsets(WindowState parent) { Rect parentFrame = parent.getFrame(); return inset(parentFrame, getActivitySystemInsets()); } private Insets getActivitySystemInsets() { getInstrumentation().waitForIdleSync(); getInstrumentation().runOnMainSync(() -> { // Excluding caption bar from system bars to fix freeform windowing mode test failures. // Non-freeform windowing modes will not be affected due to having zero caption bar. final Insets insets = mDialogTestActivity .getActivity() .getWindow() .getDecorView() .getRootWindowInsets() .getInsets(WindowInsets.Type.systemBars() & ~captionBar()); mContentInsets = Insets.of(insets.left, insets.top, insets.right, insets.bottom); }); return mContentInsets; } private static Rect inset(Rect original, Insets insets) { final int left = original.left + insets.left; final int top = original.top + insets.top; final int right = original.right - insets.right; final int bottom = original.bottom - insets.bottom; return new Rect(left, top, right, bottom); } }