• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
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_PINNED;
23 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
24 import static android.server.wm.WindowManagerState.STATE_PAUSED;
25 import static android.server.wm.WindowMetricsTestHelper.assertBoundsMatchDisplay;
26 import static android.server.wm.WindowMetricsTestHelper.getBoundsExcludingNavigationBarAndCutout;
27 import static android.server.wm.WindowMetricsTestHelper.maxWindowBoundsSandboxed;
28 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
29 
30 import static org.junit.Assert.assertEquals;
31 import static org.junit.Assert.assertTrue;
32 import static org.junit.Assert.fail;
33 import static org.junit.Assume.assumeTrue;
34 
35 import android.app.Activity;
36 import android.app.PictureInPictureParams;
37 import android.content.ComponentName;
38 import android.graphics.Point;
39 import android.graphics.Rect;
40 import android.os.Bundle;
41 import android.platform.test.annotations.Presubmit;
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 androidx.test.filters.FlakyTest;
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 CtsWindowManagerDeviceTestCases:WindowMetricsActivityTests
58  */
59 @Presubmit
60 public class WindowMetricsActivityTests extends WindowManagerTestBase {
61     private static final Rect WINDOW_BOUNDS = new Rect(100, 100, 400, 400);
62     private static final Rect RESIZED_WINDOW_BOUNDS = new Rect(100, 100, 900, 900);
63     private static final int MOVE_OFFSET = 100;
64 
65     @Test
testMetricsMatchesLayoutOnActivityOnCreate()66     public void testMetricsMatchesLayoutOnActivityOnCreate() {
67         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
68                 MetricsActivity.class);
69         final OnLayoutChangeListener listener = activity.mListener;
70 
71         listener.waitForLayout();
72 
73         WindowMetricsTestHelper.assertMetricsMatchesLayout(activity.mOnCreateCurrentMetrics,
74                 activity.mOnCreateMaximumMetrics, listener.getLayoutBounds(),
75                 listener.getLayoutInsets());
76     }
77 
78     @Test
testMetricsMatchesDisplayAreaOnActivity()79     public void testMetricsMatchesDisplayAreaOnActivity() {
80         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
81                 MetricsActivity.class);
82 
83         assertMetricsValidity(activity);
84     }
85 
86     @Test
87     @FlakyTest(bugId = 188207199)
testMetricsMatchesActivityBoundsOnNonresizableActivity()88     public void testMetricsMatchesActivityBoundsOnNonresizableActivity() {
89         assumeTrue("Skipping test: no rotation support", supportsRotation());
90 
91         final MinAspectRatioActivity activity = startActivityInWindowingModeFullScreen(
92                 MinAspectRatioActivity.class);
93         mWmState.computeState(activity.getComponentName());
94 
95         assertMetricsValidityForNonresizableActivity(activity);
96     }
97 
98     @Test
testMetricsMatchesLayoutOnPipActivity()99     public void testMetricsMatchesLayoutOnPipActivity() {
100         assumeTrue(supportsPip());
101 
102         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
103                 MetricsActivity.class);
104 
105         assertMetricsMatchesLayout(activity);
106 
107         activity.enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
108         waitForEnterPipAnimationComplete(activity.getComponentName());
109 
110         assertMetricsMatchesLayout(activity);
111     }
112 
113     @Test
testMetricsMatchesDisplayAreaOnPipActivity()114     public void testMetricsMatchesDisplayAreaOnPipActivity() {
115         assumeTrue(supportsPip());
116 
117         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
118                 MetricsActivity.class);
119 
120         assertMetricsValidity(activity);
121 
122         activity.enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
123         waitForEnterPipAnimationComplete(activity.getComponentName());
124 
125         assertMetricsValidity(activity);
126     }
127 
128     /**
129      * Waits until the picture-in-picture animation has finished.
130      */
waitForEnterPipAnimationComplete(ComponentName activityName)131     private void waitForEnterPipAnimationComplete(ComponentName activityName) {
132         waitForEnterPip(activityName);
133         mWmState.waitForWithAmState(wmState -> {
134             WindowManagerState.Task task = wmState.getTaskByActivity(activityName);
135             if (task == null) {
136                 return false;
137             }
138             WindowManagerState.Activity activity = task.getActivity(activityName);
139             return activity.getWindowingMode() == WINDOWING_MODE_PINNED
140                     && activity.getState().equals(STATE_PAUSED);
141         }, "checking activity windowing mode");
142     }
143 
144     /**
145      * Waits until the given activity has entered picture-in-picture mode (allowing for the
146      * subsequent animation to start).
147      */
waitForEnterPip(ComponentName activityName)148     private void waitForEnterPip(ComponentName activityName) {
149         mWmState.waitForWithAmState(wmState -> {
150             WindowManagerState.Task task = wmState.getTaskByActivity(activityName);
151             return task != null && task.getWindowingMode() == WINDOWING_MODE_PINNED;
152         }, "checking task windowing mode");
153     }
154 
155     @Test
testMetricsMatchesLayoutOnSplitActivity()156     public void testMetricsMatchesLayoutOnSplitActivity() {
157         assumeTrue(supportsSplitScreenMultiWindow());
158 
159         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
160                 MetricsActivity.class);
161 
162         assertMetricsMatchesLayout(activity);
163 
164         mWmState.computeState(activity.getComponentName());
165         putActivityInPrimarySplit(activity.getComponentName());
166 
167         mWmState.computeState(activity.getComponentName());
168         assertEquals(WINDOWING_MODE_MULTI_WINDOW,
169                 mWmState.getActivity(activity.getComponentName()).getWindowingMode());
170 
171         assertMetricsMatchesLayout(activity);
172     }
173 
174     @Test
testMetricsMatchesDisplayAreaOnSplitActivity()175     public void testMetricsMatchesDisplayAreaOnSplitActivity() {
176         assumeTrue(supportsSplitScreenMultiWindow());
177 
178         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
179                 MetricsActivity.class);
180 
181         assertMetricsValidity(activity);
182 
183         mWmState.computeState(activity.getComponentName());
184         putActivityInPrimarySplit(activity.getComponentName());
185 
186         mWmState.computeState(activity.getComponentName());
187         assertTrue(mWmState.getActivity(activity.getComponentName()).getWindowingMode()
188                 == WINDOWING_MODE_MULTI_WINDOW);
189 
190         assertMetricsValidity(activity);
191     }
192 
193     @Test
testMetricsMatchesLayoutOnFreeformActivity()194     public void testMetricsMatchesLayoutOnFreeformActivity() {
195         assumeTrue(supportsFreeform());
196 
197         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
198                 MetricsActivity.class);
199 
200         assertMetricsMatchesLayout(activity);
201 
202         launchActivity(new ComponentName(mTargetContext, MetricsActivity.class),
203                 WINDOWING_MODE_FREEFORM);
204 
205         // Resize the freeform activity.
206         resizeActivityTask(activity.getComponentName(), WINDOW_BOUNDS.left, WINDOW_BOUNDS.top,
207                 WINDOW_BOUNDS.right, WINDOW_BOUNDS.bottom);
208 
209         assertMetricsMatchesLayout(activity);
210 
211         // Resize again.
212         resizeActivityTask(activity.getComponentName(), RESIZED_WINDOW_BOUNDS.left,
213                 RESIZED_WINDOW_BOUNDS.top, RESIZED_WINDOW_BOUNDS.right,
214                 RESIZED_WINDOW_BOUNDS.bottom);
215 
216         assertMetricsMatchesLayout(activity);
217 
218         // Move the activity.
219         resizeActivityTask(activity.getComponentName(), MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.left,
220                 MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.top, MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.right,
221                 MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.bottom);
222 
223         assertMetricsMatchesLayout(activity);
224     }
225 
226     @Test
testMetricsMatchesDisplayAreaOnFreeformActivity()227     public void testMetricsMatchesDisplayAreaOnFreeformActivity() {
228         assumeTrue(supportsFreeform());
229 
230         final MetricsActivity activity = startActivityInWindowingModeFullScreen(
231                 MetricsActivity.class);
232 
233         assertMetricsValidity(activity);
234 
235         launchActivity(new ComponentName(mTargetContext, MetricsActivity.class),
236                 WINDOWING_MODE_FREEFORM);
237 
238         // Resize the freeform activity.
239         resizeActivityTask(activity.getComponentName(), WINDOW_BOUNDS.left, WINDOW_BOUNDS.top,
240                 WINDOW_BOUNDS.right, WINDOW_BOUNDS.bottom);
241 
242         assertMetricsValidity(activity);
243 
244         // Resize again.
245         resizeActivityTask(activity.getComponentName(), RESIZED_WINDOW_BOUNDS.left,
246                 RESIZED_WINDOW_BOUNDS.top, RESIZED_WINDOW_BOUNDS.right,
247                 RESIZED_WINDOW_BOUNDS.bottom);
248 
249         assertMetricsValidity(activity);
250 
251         // Move the activity.
252         resizeActivityTask(activity.getComponentName(), MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.left,
253                 MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.top, MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.right,
254                 MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.bottom);
255 
256         assertMetricsValidity(activity);
257     }
258 
assertMetricsMatchesLayout(MetricsActivity activity)259     private static void assertMetricsMatchesLayout(MetricsActivity activity) {
260         final OnLayoutChangeListener listener = activity.mListener;
261         listener.waitForLayout();
262 
263         final Supplier<WindowMetrics> currentMetrics =
264                 () -> activity.getWindowManager().getCurrentWindowMetrics();
265         final Supplier<WindowMetrics> maxMetrics =
266                 () -> activity.getWindowManager().getMaximumWindowMetrics();
267 
268         Condition.waitFor(new Condition<>("WindowMetrics must match layout metrics",
269                 () -> currentMetrics.get().getBounds().equals(listener.getLayoutBounds()))
270                 .setRetryIntervalMs(500).setRetryLimit(10)
271                 .setOnFailure(unused -> fail("WindowMetrics must match layout metrics. Layout"
272                         + "bounds is " + listener.getLayoutBounds() + ", while current window"
273                         + "metrics is " + currentMetrics.get().getBounds())));
274 
275         final int windowingMode = activity.getResources().getConfiguration().windowConfiguration
276                 .getWindowingMode();
277         WindowMetricsTestHelper.assertMetricsMatchesLayout(currentMetrics.get(), maxMetrics.get(),
278                 listener.getLayoutBounds(), listener.getLayoutInsets(),
279                 inMultiWindowMode(windowingMode));
280     }
281 
282     // Copied from WindowConfiguration#inMultiWindowMode(int windowingMode)
283     // TODO(b/250741386): make it a @TestApi in U
inMultiWindowMode(int windowingMode)284     private static boolean inMultiWindowMode(int windowingMode) {
285         return windowingMode != WINDOWING_MODE_FULLSCREEN
286                 && windowingMode != WINDOWING_MODE_UNDEFINED;
287     }
288 
289     /**
290      * Verifies two scenarios for an {@link Activity}. If the activity is freeform, then the bounds
291      * should not include insets for navigation bar and cutout area.
292      * <ul>
293      *     <li>{@link WindowManager#getCurrentWindowMetrics()} matches
294      *     {@link Display#getSize(Point)}</li>
295      *     <li>{@link WindowManager#getMaximumWindowMetrics()} and {@link Display#getSize(Point)}
296      *     either matches DisplayArea bounds which the {@link Activity} is attached to, or matches
297      *     {@link WindowManager#getCurrentWindowMetrics()} if sandboxing is applied.</li>
298      * </ul>
299      */
assertMetricsValidity(Activity activity)300     private void assertMetricsValidity(Activity activity) {
301         mWmState.computeState(activity.getComponentName());
302         WindowMetricsTestHelper.assertMetricsValidity(activity,
303                 getTaskDisplayAreaBounds(activity.getComponentName()));
304     }
305 
306     /**
307      * Verifies two scenarios for a non-resizable {@link Activity}. Similar to
308      * {@link #assertMetricsValidity(Activity)}, verifies the values of window metrics against
309      * Display size. {@link WindowManager#getMaximumWindowMetrics()} must match activity bounds
310      * if the activity is sandboxed.
311      *
312      * App bounds calculation of a nonresizable activity depends on the orientation of the display
313      * and the app, and the display and activity bounds. Directly compare maximum WindowMetrics
314      * against the value used for sandboxing, since other ways of accessing activity bounds may
315      * have different insets applied.
316      *
317      * @param activity the activity under test
318      */
assertMetricsValidityForNonresizableActivity(Activity activity)319     private void assertMetricsValidityForNonresizableActivity(Activity activity) {
320         ComponentName activityName = activity.getComponentName();
321         mWmState.computeState(activityName);
322         WindowManagerState.Activity activityContainer = mWmState.getActivity(activityName);
323         final boolean boundsShouldIncludeInsets =
324                 activity.getResources().getConfiguration().windowConfiguration
325                         .getWindowingMode() == WINDOWING_MODE_FREEFORM
326                         || activityContainer.inSizeCompatMode();
327         final WindowManager windowManager = activity.getWindowManager();
328         final WindowMetrics currentMetrics = windowManager.getCurrentWindowMetrics();
329         final Rect currentBounds = boundsShouldIncludeInsets ? currentMetrics.getBounds()
330                 : getBoundsExcludingNavigationBarAndCutout(currentMetrics);
331         final Rect maxBounds = windowManager.getMaximumWindowMetrics().getBounds();
332         final Display display = activity.getDisplay();
333 
334         assertBoundsMatchDisplay(maxBounds, currentBounds, display);
335 
336         // Max window bounds should match either DisplayArea bounds, or activity bounds if it is
337         // sandboxed.
338         final Rect displayAreaBounds = getTaskDisplayAreaBounds(activityName);
339         final Rect activityBounds =
340                 activityContainer.mFullConfiguration.windowConfiguration.getBounds();
341         if (maxWindowBoundsSandboxed(displayAreaBounds, maxBounds)) {
342             // Max window bounds are sandboxed, so max window bounds should match activity bounds.
343             assertEquals("Maximum window metrics of a non-resizable activity matches "
344                     + "activity bounds, when sandboxed", activityBounds, maxBounds);
345         } else {
346             // Max window bounds are not sandboxed, so max window bounds should match display area
347             // bounds.
348             assertEquals("Display area bounds must match max window size", displayAreaBounds,
349                     maxBounds);
350         }
351     }
352 
getTaskDisplayAreaBounds(ComponentName activityName)353     private Rect getTaskDisplayAreaBounds(ComponentName activityName) {
354         WindowManagerState.DisplayArea tda = mWmState.getTaskDisplayArea(activityName);
355         return tda.mFullConfiguration.windowConfiguration.getBounds();
356     }
357 
358     public static class MetricsActivity extends FocusableActivity {
359         private WindowMetrics mOnCreateMaximumMetrics;
360         private WindowMetrics mOnCreateCurrentMetrics;
361         private final OnLayoutChangeListener mListener = new OnLayoutChangeListener();
362 
363         @Override
onCreate(Bundle savedInstanceState)364         protected void onCreate(Bundle savedInstanceState) {
365             super.onCreate(savedInstanceState);
366             mOnCreateCurrentMetrics = getWindowManager().getCurrentWindowMetrics();
367             mOnCreateMaximumMetrics = getWindowManager().getMaximumWindowMetrics();
368             getWindow().getDecorView().addOnLayoutChangeListener(mListener);
369 
370             // Always extend the cutout areas because layout doesn't get the waterfall cutout.
371             final WindowManager.LayoutParams attrs = getWindow().getAttributes();
372             attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
373             getWindow().setAttributes(attrs);
374         }
375 
376         @Override
onDestroy()377         protected void onDestroy() {
378             super.onDestroy();
379             getWindow().getDecorView().removeOnLayoutChangeListener(mListener);
380         }
381     }
382 
383     public static class MinAspectRatioActivity extends MetricsActivity {
384     }
385 }
386