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