1 /*
2  * Copyright (C) 2020 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.window;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
23 import static android.server.wm.WindowMetricsTestHelper.assertMetricsMatchDisplay;
24 import static android.server.wm.WindowMetricsTestHelper.maxWindowBoundsSandboxed;
25 
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertTrue;
28 import static org.junit.Assert.fail;
29 import static org.junit.Assume.assumeTrue;
30 
31 import android.app.Activity;
32 import android.app.PictureInPictureParams;
33 import android.content.ComponentName;
34 import android.graphics.Point;
35 import android.graphics.Rect;
36 import android.platform.test.annotations.Presubmit;
37 import android.server.wm.Condition;
38 import android.server.wm.MetricsActivity;
39 import android.server.wm.WindowManagerState;
40 import android.server.wm.WindowManagerTestBase;
41 import android.server.wm.WindowMetricsTestHelper;
42 import android.server.wm.WindowMetricsTestHelper.OnLayoutChangeListener;
43 import android.view.Display;
44 import android.view.WindowManager;
45 import android.view.WindowMetrics;
46 
47 import com.android.compatibility.common.util.ApiTest;
48 
49 import org.junit.Test;
50 
51 import java.util.function.Supplier;
52 
53 /**
54  * Tests that verify the behavior of {@link WindowMetrics} APIs on {@link Activity activities}.
55  *
56  * Build/Install/Run:
57  *     atest CtsWindowManagerDeviceWindow:WindowMetricsActivityTests
58  */
59 @Presubmit
60 @ApiTest(apis = {"android.view.WindowManager#getCurrentWindowMetrics",
61         "android.view.WindowManager#getMaximumWindowMetrics",
62         "android.app.Activity#getWindowManager"})
63 public class WindowMetricsActivityTests extends WindowManagerTestBase {
64     private static final Rect WINDOW_BOUNDS = new Rect(100, 100, 900, 900);
65     private static final int MOVE_OFFSET = 100;
66 
67     @Test
testMetricsMatchesLayoutOnActivityOnCreate()68     public void testMetricsMatchesLayoutOnActivityOnCreate() {
69         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
70                 MetricsActivity.class);
71         final OnLayoutChangeListener listener = activity.getListener();
72 
73         listener.waitForLayout();
74 
75         WindowMetricsTestHelper.assertMetricsMatchesLayout(
76                 activity.getOnCreateCurrentMetrics(),
77                 activity.getOnCreateMaximumMetrics(),
78                 listener.getLayoutBounds(),
79                 listener.getLayoutInsets());
80     }
81 
82     @Test
testMetricsMatchesDisplayAreaOnActivity()83     public void testMetricsMatchesDisplayAreaOnActivity() {
84         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
85                 MetricsActivity.class);
86 
87         assertMetricsValidity(activity);
88     }
89 
90     @Test
testMetricsMatchesActivityBoundsOnNonresizableActivity()91     public void testMetricsMatchesActivityBoundsOnNonresizableActivity() {
92         assumeTrue("Skipping test: no rotation support", supportsRotation());
93 
94         final MinAspectRatioActivity activity = startActivityInWindowingModeFullScreen(
95                 MinAspectRatioActivity.class);
96         mWmState.computeState(activity.getComponentName());
97 
98         assertMetricsValidityForNonresizableActivity(activity);
99     }
100 
101     @Test
testMetricsMatchesLayoutOnPipActivity()102     public void testMetricsMatchesLayoutOnPipActivity() {
103         assumeTrue(supportsPip());
104 
105         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
106                 MetricsActivity.class);
107 
108         assertMetricsMatchesLayout(activity);
109 
110         activity.enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
111         waitForEnterPipAnimationComplete(activity.getComponentName());
112 
113         assertMetricsMatchesLayout(activity);
114     }
115 
116     @Test
testMetricsMatchesDisplayAreaOnPipActivity()117     public void testMetricsMatchesDisplayAreaOnPipActivity() {
118         assumeTrue(supportsPip());
119 
120         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
121                 MetricsActivity.class);
122 
123         assertMetricsValidity(activity);
124 
125         activity.enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
126         waitForEnterPipAnimationComplete(activity.getComponentName());
127 
128         assertMetricsValidity(activity);
129     }
130 
131     @Test
testMetricsMatchesLayoutOnSplitActivity()132     public void testMetricsMatchesLayoutOnSplitActivity() {
133         assumeTrue(supportsSplitScreenMultiWindow());
134 
135         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
136                 MetricsActivity.class);
137 
138         assertMetricsMatchesLayout(activity);
139 
140         mWmState.computeState(activity.getComponentName());
141         putActivityInPrimarySplit(activity.getComponentName());
142 
143         mWmState.computeState(activity.getComponentName());
144         assertEquals(WINDOWING_MODE_MULTI_WINDOW,
145                 mWmState.getActivity(activity.getComponentName()).getWindowingMode());
146 
147         assertMetricsMatchesLayout(activity);
148     }
149 
150     @Test
testMetricsMatchesDisplayAreaOnSplitActivity()151     public void testMetricsMatchesDisplayAreaOnSplitActivity() {
152         assumeTrue(supportsSplitScreenMultiWindow());
153 
154         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
155                 MetricsActivity.class);
156 
157         assertMetricsValidity(activity);
158 
159         mWmState.computeState(activity.getComponentName());
160         putActivityInPrimarySplit(activity.getComponentName());
161 
162         mWmState.computeState(activity.getComponentName());
163         assertTrue(mWmState.getActivity(activity.getComponentName()).getWindowingMode()
164                 == WINDOWING_MODE_MULTI_WINDOW);
165 
166         assertMetricsValidity(activity);
167     }
168 
169     @Test
testMetricsMatchesLayoutOnFreeformActivity()170     public void testMetricsMatchesLayoutOnFreeformActivity() {
171         assumeTrue(supportsFreeform());
172 
173         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
174                 MetricsActivity.class);
175 
176         assertMetricsMatchesLayout(activity);
177 
178         launchActivity(new ComponentName(mTargetContext, MetricsActivity.class),
179                 WINDOWING_MODE_FREEFORM);
180 
181         Condition.waitFor(new Condition<>("Activity becomes freeform mode",
182                 activity::isInMultiWindowMode)
183                 .setRetryIntervalMs(500).setRetryLimit(10)
184                 .setOnFailure(unused -> fail("Activity must be in multi-window mode.")));
185         assertMetricsMatchesLayout(activity);
186 
187         final Rect boundsBeforeResize = activity.getListener().getLayoutBounds();
188         // Resize the task.
189         resizeActivityTask(activity.getComponentName(), WINDOW_BOUNDS.left, WINDOW_BOUNDS.top,
190                 WINDOW_BOUNDS.right, WINDOW_BOUNDS.bottom);
191 
192         Condition.waitFor(new Condition<>("layout bounds change",
193                 () -> !boundsBeforeResize.equals(activity.getListener().getLayoutBounds()))
194                 .setRetryIntervalMs(500).setRetryLimit(10)
195                 .setOnFailure(unused -> fail("Layout bounds must be changed due to task resize.")));
196         assertMetricsMatchesLayout(activity);
197 
198         final Rect boundsBeforeMove = activity.getListener().getLayoutBounds();
199         // Move the task.
200         resizeActivityTask(activity.getComponentName(), MOVE_OFFSET + WINDOW_BOUNDS.left,
201                 MOVE_OFFSET + WINDOW_BOUNDS.top, MOVE_OFFSET + WINDOW_BOUNDS.right,
202                 MOVE_OFFSET + WINDOW_BOUNDS.bottom);
203 
204         Condition.waitFor(new Condition<>("layout bounds change",
205                 () -> !boundsBeforeMove.equals(activity.getListener().getLayoutBounds()))
206                 .setRetryIntervalMs(500).setRetryLimit(10)
207                 .setOnFailure(unused -> fail("Layout bounds must be changed due to task move.")));
208         assertMetricsMatchesLayout(activity);
209     }
210 
211     @Test
testMetricsMatchesDisplayAreaOnFreeformActivity()212     public void testMetricsMatchesDisplayAreaOnFreeformActivity() {
213         assumeTrue(supportsFreeform());
214 
215         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
216                 MetricsActivity.class);
217 
218         assertMetricsValidity(activity);
219 
220         launchActivity(new ComponentName(mTargetContext, MetricsActivity.class),
221                 WINDOWING_MODE_FREEFORM);
222 
223         // Resize the task.
224         resizeActivityTask(activity.getComponentName(), WINDOW_BOUNDS.left, WINDOW_BOUNDS.top,
225                 WINDOW_BOUNDS.right, WINDOW_BOUNDS.bottom);
226 
227         assertMetricsValidity(activity);
228 
229         // Move the task.
230         resizeActivityTask(activity.getComponentName(), MOVE_OFFSET + WINDOW_BOUNDS.left,
231                 MOVE_OFFSET + WINDOW_BOUNDS.top, MOVE_OFFSET + WINDOW_BOUNDS.right,
232                 MOVE_OFFSET + WINDOW_BOUNDS.bottom);
233 
234         assertMetricsValidity(activity);
235     }
236 
assertMetricsMatchesLayout(MetricsActivity activity)237     private static void assertMetricsMatchesLayout(MetricsActivity activity) {
238         final OnLayoutChangeListener listener = activity.getListener();
239         listener.waitForLayout();
240 
241         final Supplier<WindowMetrics> currentMetrics =
242                 () -> activity.getWindowManager().getCurrentWindowMetrics();
243         final Supplier<WindowMetrics> maxMetrics =
244                 () -> activity.getWindowManager().getMaximumWindowMetrics();
245 
246         Condition.waitFor(new Condition<>("WindowMetrics matches layout metrics",
247                 () -> currentMetrics.get().getBounds().equals(listener.getLayoutBounds()))
248                 .setRetryIntervalMs(500).setRetryLimit(10)
249                 .setOnFailure(unused -> fail("WindowMetrics must match layout metrics. Layout"
250                         + "bounds is " + listener.getLayoutBounds() + ", while current window"
251                         + "metrics is " + currentMetrics.get().getBounds())));
252 
253         final int windowingMode = activity.getResources().getConfiguration().windowConfiguration
254                 .getWindowingMode();
255         WindowMetricsTestHelper.assertMetricsMatchesLayout(currentMetrics.get(), maxMetrics.get(),
256                 listener.getLayoutBounds(), listener.getLayoutInsets(),
257                 inMultiWindowMode(windowingMode));
258     }
259 
260     // Copied from WindowConfiguration#inMultiWindowMode(int windowingMode)
261     // TODO(b/250741386): make it a @TestApi in U
inMultiWindowMode(int windowingMode)262     private static boolean inMultiWindowMode(int windowingMode) {
263         return windowingMode != WINDOWING_MODE_FULLSCREEN
264                 && windowingMode != WINDOWING_MODE_UNDEFINED;
265     }
266 
267     /**
268      * Verifies two scenarios for an {@link Activity}. If the activity is freeform, then the bounds
269      * should not include insets for navigation bar and cutout area.
270      * <ul>
271      *     <li>{@link WindowManager#getCurrentWindowMetrics()} matches
272      *     {@link Display#getSize(Point)}</li>
273      *     <li>{@link WindowManager#getMaximumWindowMetrics()} and {@link Display#getSize(Point)}
274      *     either matches DisplayArea bounds which the {@link Activity} is attached to, or matches
275      *     {@link WindowManager#getCurrentWindowMetrics()} if sandboxing is applied.</li>
276      * </ul>
277      */
assertMetricsValidity(Activity activity)278     private void assertMetricsValidity(Activity activity) {
279         mWmState.computeState(activity.getComponentName());
280         WindowMetricsTestHelper.assertMetricsValidity(activity,
281                 getTaskDisplayAreaBounds(activity.getComponentName()));
282     }
283 
284     /**
285      * Verifies two scenarios for a non-resizable {@link Activity}. Similar to
286      * {@link #assertMetricsValidity(Activity)}, verifies the values of window metrics against
287      * Display size. {@link WindowManager#getMaximumWindowMetrics()} must match activity bounds
288      * if the activity is sandboxed.
289      *
290      * App bounds calculation of a nonresizable activity depends on the orientation of the display
291      * and the app, and the display and activity bounds. Directly compare maximum WindowMetrics
292      * against the value used for sandboxing, since other ways of accessing activity bounds may
293      * have different insets applied.
294      *
295      * @param activity the activity under test
296      */
assertMetricsValidityForNonresizableActivity(Activity activity)297     private void assertMetricsValidityForNonresizableActivity(Activity activity) {
298         ComponentName activityName = activity.getComponentName();
299         mWmState.computeState(activityName);
300         WindowManagerState.Activity activityContainer = mWmState.getActivity(activityName);
301         final boolean shouldBoundsIncludeInsets =
302                 activity.getResources().getConfiguration().windowConfiguration
303                         .getWindowingMode() == WINDOWING_MODE_FREEFORM
304                         || activityContainer.inSizeCompatMode();
305         final WindowManager windowManager = activity.getWindowManager();
306         final WindowMetrics currentMetrics = windowManager.getCurrentWindowMetrics();
307         final WindowMetrics maxMetrics = windowManager.getMaximumWindowMetrics();
308 
309         final Rect maxBounds = windowManager.getMaximumWindowMetrics().getBounds();
310         final Display display = activity.getDisplay();
311 
312         assertMetricsMatchDisplay(maxMetrics, currentMetrics, display, shouldBoundsIncludeInsets);
313 
314         // Max window bounds should match either DisplayArea bounds, or activity bounds if it is
315         // sandboxed.
316         final Rect displayAreaBounds = getTaskDisplayAreaBounds(activityName);
317         final Rect activityBounds =
318                 activityContainer.getFullConfiguration().windowConfiguration.getBounds();
319         if (maxWindowBoundsSandboxed(displayAreaBounds, maxBounds)) {
320             // Max window bounds are sandboxed, so max window bounds should match activity bounds.
321             assertEquals("Maximum window metrics of a non-resizable activity matches "
322                     + "activity bounds, when sandboxed", activityBounds, maxBounds);
323         } else {
324             // Max window bounds are not sandboxed, so max window bounds should match display area
325             // bounds.
326             assertEquals("Display area bounds must match max window size", displayAreaBounds,
327                     maxBounds);
328         }
329     }
330 
getTaskDisplayAreaBounds(ComponentName activityName)331     private Rect getTaskDisplayAreaBounds(ComponentName activityName) {
332         WindowManagerState.DisplayArea tda = mWmState.getTaskDisplayArea(activityName);
333         return tda.getFullConfiguration().windowConfiguration.getBounds();
334     }
335 
336     public static class MinAspectRatioActivity extends MetricsActivity {
337     }
338 }
339