1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.server.wm;
18 
19 import static android.server.wm.ComponentNameUtils.getWindowName;
20 import static android.server.wm.DialogFrameTestActivity.DIALOG_WINDOW_NAME;
21 import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_POSITION_MATCH_PARENT;
22 import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_POSITION_MATCH_PARENT_NO_LIMITS;
23 import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_SIZE;
24 import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_SIZE_BOTTOM_RIGHT_GRAVITY;
25 import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_SIZE_TOP_LEFT_GRAVITY;
26 import static android.server.wm.DialogFrameTestActivity.TEST_MATCH_PARENT;
27 import static android.server.wm.DialogFrameTestActivity.TEST_NO_FOCUS;
28 import static android.server.wm.DialogFrameTestActivity.TEST_OVER_SIZED_DIMENSIONS;
29 import static android.server.wm.DialogFrameTestActivity.TEST_OVER_SIZED_DIMENSIONS_NO_LIMITS;
30 import static android.server.wm.DialogFrameTestActivity.TEST_WITH_MARGINS;
31 import static android.view.WindowInsets.Type.captionBar;
32 
33 import static androidx.test.InstrumentationRegistry.getInstrumentation;
34 
35 import static org.hamcrest.MatcherAssert.assertThat;
36 import static org.hamcrest.Matchers.greaterThan;
37 import static org.junit.Assert.assertEquals;
38 
39 import android.content.ComponentName;
40 import android.graphics.Insets;
41 import android.graphics.Rect;
42 import android.platform.test.annotations.AppModeFull;
43 import android.platform.test.annotations.Presubmit;
44 import android.server.wm.WindowManagerState.WindowState;
45 import android.view.WindowInsets;
46 
47 import androidx.test.rule.ActivityTestRule;
48 
49 import org.junit.Ignore;
50 import org.junit.Rule;
51 import org.junit.Test;
52 
53 import java.util.List;
54 
55 /**
56  * Build/Install/Run:
57  *     atest CtsWindowManagerDeviceTestCases:DialogFrameTests
58  *
59  * TODO: Consolidate this class with {@link ParentChildTestBase}.
60  */
61 @AppModeFull(reason = "Requires android.permission.MANAGE_ACTIVITY_TASKS")
62 @Presubmit
63 public class DialogFrameTests extends ParentChildTestBase<DialogFrameTestActivity> {
64 
65     private static final ComponentName DIALOG_FRAME_TEST_ACTIVITY = new ComponentName(
66             getInstrumentation().getContext(), DialogFrameTestActivity.class);
67     private Insets mContentInsets;
68 
69     @Rule
70     public final ActivityTestRule<DialogFrameTestActivity> mDialogTestActivity =
71             new ActivityTestRule<>(DialogFrameTestActivity.class, false /* initialTOuchMode */,
72                     false /* launchActivity */);
73 
74     @Override
activityName()75     ComponentName activityName() {
76         return DIALOG_FRAME_TEST_ACTIVITY;
77     }
78 
79     @Override
activityRule()80     ActivityTestRule<DialogFrameTestActivity> activityRule() {
81         return mDialogTestActivity;
82     }
83 
getSingleWindow(final String windowName)84     private WindowState getSingleWindow(final String windowName) {
85         final List<WindowState> windowList =
86                 mWmState.getMatchingVisibleWindowState(windowName);
87         assertThat(windowList.size(), greaterThan(0));
88         return windowList.get(0);
89     }
90 
91     @Override
doSingleTest(ParentChildTest t)92     void doSingleTest(ParentChildTest t) throws Exception {
93         mWmState.computeState(WaitForValidActivityState.forWindow(DIALOG_WINDOW_NAME));
94         WindowState dialog = getSingleWindow(DIALOG_WINDOW_NAME);
95         WindowState parent = getSingleWindow(getWindowName(activityName()));
96 
97         t.doTest(parent, dialog);
98     }
99 
100     // With Width and Height as MATCH_PARENT we should fill
101     // the same content frame as the main activity window
102     @Test
testMatchParentDialog()103     public void testMatchParentDialog() throws Exception {
104         doParentChildTest(TEST_MATCH_PARENT, (parent, dialog) -> { ;
105             assertEquals(getParentFrameWithInsets(parent), dialog.getFrame());
106         });
107     }
108 
109     private static final int explicitDimension = 200;
110 
111     // The default gravity for dialogs should center them.
112     @Test
testExplicitSizeDefaultGravity()113     public void testExplicitSizeDefaultGravity() throws Exception {
114         doParentChildTest(TEST_EXPLICIT_SIZE, (parent, dialog) -> {
115             Rect parentFrame = getParentFrameWithInsets(parent);
116             Rect expectedFrame = new Rect(
117                     parentFrame.left + (parentFrame.width() - explicitDimension) / 2,
118                     parentFrame.top + (parentFrame.height() - explicitDimension) / 2,
119                     parentFrame.left + (parentFrame.width() + explicitDimension) / 2,
120                     parentFrame.top + (parentFrame.height() + explicitDimension) / 2);
121             assertEquals(expectedFrame, dialog.getFrame());
122         });
123     }
124 
125     @Test
testExplicitSizeTopLeftGravity()126     public void testExplicitSizeTopLeftGravity() throws Exception {
127         doParentChildTest(TEST_EXPLICIT_SIZE_TOP_LEFT_GRAVITY, (parent, dialog) -> {
128             Rect parentFrame = getParentFrameWithInsets(parent);
129             Rect expectedFrame = new Rect(
130                     parentFrame.left,
131                     parentFrame.top,
132                     parentFrame.left + explicitDimension,
133                     parentFrame.top + explicitDimension);
134             assertEquals(expectedFrame, dialog.getFrame());
135         });
136     }
137 
138     @Test
testExplicitSizeBottomRightGravity()139     public void testExplicitSizeBottomRightGravity() throws Exception {
140         doParentChildTest(TEST_EXPLICIT_SIZE_BOTTOM_RIGHT_GRAVITY, (parent, dialog) -> {
141             Rect parentFrame = getParentFrameWithInsets(parent);
142             Rect expectedFrame = new Rect(
143                     parentFrame.left + parentFrame.width() - explicitDimension,
144                     parentFrame.top + parentFrame.height() - explicitDimension,
145                     parentFrame.left + parentFrame.width(),
146                     parentFrame.top + parentFrame.height());
147             assertEquals(expectedFrame, dialog.getFrame());
148         });
149     }
150 
151     // TODO(b/30127373): Commented out for now because it doesn't work. We end up insetting the
152     // decor on the bottom. I think this is a bug probably in the default dialog flags:
153     @Ignore
154     @Test
testOversizedDimensions()155     public void testOversizedDimensions() throws Exception {
156         doParentChildTest(TEST_OVER_SIZED_DIMENSIONS, (parent, dialog) ->
157                 // With the default flags oversize should result in clipping to
158                 // parent frame.
159                 assertEquals(getParentFrameWithInsets(parent), dialog.getFrame())
160         );
161     }
162 
163     // TODO(b/63993863) : Disabled pending public API to fetch maximum surface size.
164     static final int oversizedDimension = 5000;
165     // With FLAG_LAYOUT_NO_LIMITS  we should get the size we request, even if its much larger than
166     // the screen.
167     @Ignore
168     @Test
testOversizedDimensionsNoLimits()169     public void testOversizedDimensionsNoLimits() throws Exception {
170         // TODO(b/36890978): We only run this in fullscreen because of the
171         // unclear status of NO_LIMITS for non-child surfaces in MW modes
172         doFullscreenTest(TEST_OVER_SIZED_DIMENSIONS_NO_LIMITS, (parent, dialog) -> {
173             Rect parentFrame = getParentFrameWithInsets(parent);
174             Rect expectedFrame = new Rect(parentFrame.left, parentFrame.top,
175                     parentFrame.left + oversizedDimension,
176                     parentFrame.top + oversizedDimension);
177             assertEquals(expectedFrame, dialog.getFrame());
178         });
179     }
180 
181     // If we request the MATCH_PARENT and a non-zero position, we wouldn't be
182     // able to fit all of our content, so we should be adjusted to just fit the
183     // content frame.
184     @Test
testExplicitPositionMatchParent()185     public void testExplicitPositionMatchParent() throws Exception {
186         doParentChildTest(TEST_EXPLICIT_POSITION_MATCH_PARENT, (parent, dialog) ->
187                 assertEquals(getParentFrameWithInsets(parent), dialog.getFrame())
188         );
189     }
190 
191     // Unless we pass NO_LIMITS in which case our requested position should
192     // be honored.
193     @Test
testExplicitPositionMatchParentNoLimits()194     public void testExplicitPositionMatchParentNoLimits() throws Exception {
195         final int explicitPosition = 100;
196         doParentChildTest(TEST_EXPLICIT_POSITION_MATCH_PARENT_NO_LIMITS, (parent, dialog) -> {
197             Rect parentFrame = getParentFrameWithInsets(parent);
198             Rect expectedFrame = new Rect(parentFrame);
199             expectedFrame.offset(explicitPosition, explicitPosition);
200             assertEquals(expectedFrame, dialog.getFrame());
201         });
202     }
203 
204     // We run the two focus tests fullscreen only because switching to the
205     // docked stack will strip away focus from the task anyway.
206     @Test
testDialogReceivesFocus()207     public void testDialogReceivesFocus() throws Exception {
208         doFullscreenTest(TEST_MATCH_PARENT, (parent, dialog) ->
209                 assertEquals(dialog.getName(), mWmState.getFocusedWindow())
210         );
211     }
212 
213     @Test
testNoFocusDialog()214     public void testNoFocusDialog() throws Exception {
215         doFullscreenTest(TEST_NO_FOCUS, (parent, dialog) ->
216                 assertEquals(parent.getName(), mWmState.getFocusedWindow())
217         );
218     }
219 
220     @Test
testMarginsArePercentagesOfContentFrame()221     public void testMarginsArePercentagesOfContentFrame() throws Exception {
222         float horizontalMargin = .10f;
223         float verticalMargin = .15f;
224         doParentChildTest(TEST_WITH_MARGINS, (parent, dialog) -> {
225             Rect frame = getParentFrameWithInsets(parent);
226             Rect expectedFrame = new Rect(
227                     (int) (horizontalMargin * frame.width() + frame.left),
228                     (int) (verticalMargin * frame.height() + frame.top),
229                     (int) (horizontalMargin * frame.width() + frame.left) + explicitDimension,
230                     (int) (verticalMargin * frame.height() + frame.top) + explicitDimension);
231             assertEquals(expectedFrame, dialog.getFrame());
232         });
233     }
234 
235     @Test
testDialogPlacedAboveParent()236     public void testDialogPlacedAboveParent() throws Exception {
237         final WindowManagerState wmState = mWmState;
238         doParentChildTest(TEST_MATCH_PARENT, (parent, dialog) ->
239                 // Not only should the dialog be higher, but it should be leave multiple layers of
240                 // space in between for DimLayers, etc...
241                 assertThat(wmState.getZOrder(dialog), greaterThan(wmState.getZOrder(parent)))
242         );
243     }
244 
getParentFrameWithInsets(WindowState parent)245     private Rect getParentFrameWithInsets(WindowState parent) {
246         Rect parentFrame = parent.getFrame();
247         return inset(parentFrame, getActivitySystemInsets());
248     }
249 
getActivitySystemInsets()250     private Insets getActivitySystemInsets() {
251         getInstrumentation().waitForIdleSync();
252         getInstrumentation().runOnMainSync(() -> {
253             // Excluding caption bar from system bars to fix freeform windowing mode test failures.
254             // Non-freeform windowing modes will not be affected due to having zero caption bar.
255             final Insets insets = mDialogTestActivity
256                 .getActivity()
257                 .getWindow()
258                 .getDecorView()
259                 .getRootWindowInsets()
260                 .getInsets(WindowInsets.Type.systemBars() & ~captionBar());
261             mContentInsets = Insets.of(insets.left, insets.top, insets.right, insets.bottom);
262       });
263       return mContentInsets;
264     }
265 
inset(Rect original, Insets insets)266     private static Rect inset(Rect original, Insets insets) {
267         final int left = original.left + insets.left;
268         final int top = original.top + insets.top;
269         final int right = original.right - insets.right;
270         final int bottom = original.bottom - insets.bottom;
271         return new Rect(left, top, right, bottom);
272     }
273 }
274