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