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