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.view.WindowInsets.Type.displayCutout;
21 import static android.view.WindowInsets.Type.navigationBars;
22 import static android.view.WindowInsets.Type.statusBars;
23 
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertTrue;
26 
27 import android.app.Activity;
28 import android.content.Context;
29 import android.graphics.Insets;
30 import android.graphics.Point;
31 import android.graphics.Rect;
32 import android.util.DisplayMetrics;
33 import android.view.Display;
34 import android.view.View;
35 import android.view.WindowInsets;
36 import android.view.WindowManager;
37 import android.view.WindowMetrics;
38 
39 import com.android.window.flags.Flags;
40 
41 import java.util.concurrent.CountDownLatch;
42 import java.util.concurrent.TimeUnit;
43 
44 /** Helper class to test {@link WindowMetrics} behaviors. */
45 public class WindowMetricsTestHelper {
assertMetricsMatchesLayout(WindowMetrics currentMetrics, WindowMetrics maxMetrics, Rect layoutBounds, WindowInsets layoutInsets)46     public static void assertMetricsMatchesLayout(WindowMetrics currentMetrics,
47             WindowMetrics maxMetrics, Rect layoutBounds, WindowInsets layoutInsets) {
48         assertMetricsMatchesLayout(currentMetrics, maxMetrics, layoutBounds, layoutInsets,
49                 false /* inMultiWindowMode */);
50     }
51 
assertMetricsMatchesLayout(WindowMetrics currentMetrics, WindowMetrics maxMetrics, Rect layoutBounds, WindowInsets layoutInsets, boolean inMultiWindowMode)52     public static void assertMetricsMatchesLayout(WindowMetrics currentMetrics,
53             WindowMetrics maxMetrics, Rect layoutBounds, WindowInsets layoutInsets,
54             boolean inMultiWindowMode) {
55         // Only validate the size portion of the bounds, regardless of the position on the screen to
56         // take into consideration multiple screen devices (e.g. the dialog is on another screen)
57         final Rect currentMetricsBounds = currentMetrics.getBounds();
58         assertEquals(layoutBounds.height(), currentMetricsBounds.height());
59         assertEquals(layoutBounds.width(), currentMetricsBounds.width());
60         // Don't verify maxMetrics or insets for an Activity that is in multi-window mode.
61         // This is because:
62         // - Multi-window mode activities doesn't guarantee max window metrics bounds is larger than
63         // the current window metrics bounds. The bounds of a multi-window mode activity is
64         // unlimited except that it must be contained in display bounds.
65         // - WindowMetrics always reports all possible insets, whereas LayoutInsets may not report
66         // insets in multi-window mode. This means that when the Activity is in multi-window mode
67         // (*not* fullscreen), the two insets can in fact be different.
68         if (inMultiWindowMode) {
69             return;
70         }
71         assertTrue(maxMetrics.getBounds().width()
72                 >= currentMetrics.getBounds().width());
73         assertTrue(maxMetrics.getBounds().height()
74                 >= currentMetrics.getBounds().height());
75         final int insetsType = statusBars() | navigationBars() | displayCutout();
76         assertEquals(layoutInsets.getInsets(insetsType),
77                 currentMetrics.getWindowInsets().getInsets(insetsType));
78         assertEquals(layoutInsets.getDisplayCutout(),
79                 currentMetrics.getWindowInsets().getDisplayCutout());
80     }
81 
82     /**
83      * Verifies two scenarios for a {@link Context}.
84      * <ul>
85      *     <li>{@link WindowManager#getCurrentWindowMetrics()} matches
86      *     {@link Display#getSize(Point)}</li>
87      *     <li>{@link WindowManager#getMaximumWindowMetrics()} and {@link Display#getSize(Point)}
88      *     either matches DisplayArea bounds which the {@link Context} is attached to, or matches
89      *     {@link WindowManager#getCurrentWindowMetrics()} if sandboxing is applied.</li>
90      * </ul>
91      * @param context the context under test
92      * @param displayAreaBounds the bounds of the DisplayArea
93      */
assertMetricsValidity(Context context, Rect displayAreaBounds)94     public static void assertMetricsValidity(Context context, Rect displayAreaBounds) {
95         final boolean isFreeForm = context instanceof Activity
96                 && context.getResources().getConfiguration().windowConfiguration
97                 .getWindowingMode() == WINDOWING_MODE_FREEFORM;
98         final WindowManager windowManager = context.getSystemService(WindowManager.class);
99         final WindowMetrics currentMetrics = windowManager.getCurrentWindowMetrics();
100         final WindowMetrics maxMetrics = windowManager.getMaximumWindowMetrics();
101         final Rect maxBounds = maxMetrics.getBounds();
102         final Display display = context.getDisplay();
103         assertMetricsMatchDisplay(maxMetrics, currentMetrics, display, isFreeForm);
104 
105         // Max window bounds should match either DisplayArea bounds, or current window bounds.
106         if (maxWindowBoundsSandboxed(displayAreaBounds, maxBounds)) {
107             final Rect currentBounds = isFreeForm ? currentMetrics.getBounds()
108                     : getBoundsExcludingInsetsTypes(currentMetrics, configExcludedInsetsTypes());
109             // Max window bounds are sandboxed, so max window bounds and real display size
110             // should match current window bounds.
111             assertEquals("Max window size matches current window size, due to sandboxing",
112                     currentBounds, maxBounds);
113         } else {
114             // Max window bounds are not sandboxed, so max window bounds and real display size
115             // should match display area bounds.
116             assertEquals("Display area bounds must match max window size",
117                     displayAreaBounds, maxBounds);
118         }
119     }
120 
121     /**
122      * Verifies for the provided {@link WindowMetrics} and {@link Display}:
123      * <ul>
124      *     <li>Bounds and density from {@link WindowManager#getCurrentWindowMetrics()} matches
125      *     {@link Display#getMetrics(DisplayMetrics)}</li>
126      *     <li>Bounds and density from {@link WindowManager#getMaximumWindowMetrics()} matches
127      *     {@link Display#getRealMetrics(DisplayMetrics)}
128      *
129      * </ul>
130      * @param maxMetrics the {@link WindowMetrics} from
131      *                  {@link WindowManager#getMaximumWindowMetrics()}
132      * @param currentMetrics the {@link WindowMetrics} from
133      *                      {@link WindowManager#getCurrentWindowMetrics()}
134      * @param display the display to compare bounds against
135      * @param shouldBoundsIncludeInsets whether the bounds to be verified should include insets
136      */
assertMetricsMatchDisplay( WindowMetrics maxMetrics, WindowMetrics currentMetrics, Display display, boolean shouldBoundsIncludeInsets)137     public static void assertMetricsMatchDisplay(
138             WindowMetrics maxMetrics,
139             WindowMetrics currentMetrics,
140             Display display,
141             boolean shouldBoundsIncludeInsets) {
142         // Check window bounds
143         final DisplayMetrics displayMetrics = new DisplayMetrics();
144         display.getMetrics(displayMetrics);
145         final Rect currentBounds = shouldBoundsIncludeInsets ? currentMetrics.getBounds()
146                 : getBoundsExcludingInsetsTypes(currentMetrics, configExcludedInsetsTypes());
147         assertEquals("Reported display width must match window width",
148                 displayMetrics.widthPixels, currentBounds.width());
149         assertEquals("Reported display height must match window height",
150                 displayMetrics.heightPixels, currentBounds.height());
151         assertEquals("Reported display density must match density from window metrics",
152                 displayMetrics.density, currentMetrics.getDensity(), 0.0f);
153 
154         // Max window bounds should match real display size.
155         final DisplayMetrics realDisplayMetrics = new DisplayMetrics();
156         display.getRealMetrics(realDisplayMetrics);
157         final Rect maxBounds = maxMetrics.getBounds();
158         assertEquals("Reported real display width must match max window width",
159                 realDisplayMetrics.widthPixels, maxBounds.width());
160         assertEquals("Reported real display height must match max window height",
161                 realDisplayMetrics.heightPixels, maxBounds.height());
162         assertEquals("Reported real display density must match density from window metrics",
163                 realDisplayMetrics.density, currentMetrics.getDensity(), 0.0f);
164     }
165 
getBoundsExcludingInsetsTypes(WindowMetrics windowMetrics, int excludingTypes)166     public static Rect getBoundsExcludingInsetsTypes(WindowMetrics windowMetrics,
167             int excludingTypes) {
168         WindowInsets windowInsets = windowMetrics.getWindowInsets();
169         final Insets excludingInsets =
170                 windowInsets.getInsetsIgnoringVisibility(excludingTypes);
171 
172         final Rect bounds = windowMetrics.getBounds();
173         return inset(bounds, excludingInsets);
174     }
175 
176     /**
177      * Returns {@code true} if the bounds from {@link WindowManager#getMaximumWindowMetrics()} are
178      * sandboxed, so are smaller than the DisplayArea.
179      */
maxWindowBoundsSandboxed(Rect displayAreaBounds, Rect maxBounds)180     public static boolean maxWindowBoundsSandboxed(Rect displayAreaBounds, Rect maxBounds) {
181         return maxBounds.width() < displayAreaBounds.width()
182                 || maxBounds.height() < displayAreaBounds.height();
183     }
184 
inset(Rect original, Insets insets)185     private static Rect inset(Rect original, Insets insets) {
186         final int left = original.left + insets.left;
187         final int top = original.top + insets.top;
188         final int right = original.right - insets.right;
189         final int bottom = original.bottom - insets.bottom;
190         return new Rect(left, top, right, bottom);
191     }
192 
configExcludedInsetsTypes()193     private static int configExcludedInsetsTypes() {
194         // If the insets is decoupled from configuration, no insets type should be excluded.
195         if (Flags.insetsDecoupledConfiguration()) {
196             return 0;
197         }
198         return navigationBars() | displayCutout();
199     }
200 
201     public static class OnLayoutChangeListener implements View.OnLayoutChangeListener {
202         private CountDownLatch mLayoutLatch = new CountDownLatch(1);
203 
204         private volatile Rect mOnLayoutBoundsInScreen;
205         private volatile WindowInsets mOnLayoutInsets;
206 
207         @Override
onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)208         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
209                 int oldTop, int oldRight, int oldBottom) {
210             synchronized (this) {
211                 mOnLayoutBoundsInScreen = new Rect(left, top, right, bottom);
212                 // Convert decorView's bounds from window coordinates to screen coordinates.
213                 final int[] locationOnScreen = new int[2];
214                 v.getLocationOnScreen(locationOnScreen);
215                 mOnLayoutBoundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
216 
217                 mOnLayoutInsets = v.getRootWindowInsets();
218                 mLayoutLatch.countDown();
219             }
220         }
221 
getLayoutBounds()222         public Rect getLayoutBounds() {
223             synchronized (this) {
224                 return mOnLayoutBoundsInScreen;
225             }
226         }
227 
getLayoutInsets()228         public WindowInsets getLayoutInsets() {
229             synchronized (this) {
230                 return mOnLayoutInsets;
231             }
232         }
233 
waitForLayout()234         public void waitForLayout() {
235             try {
236                 assertTrue("Timed out waiting for layout.",
237                         mLayoutLatch.await(4, TimeUnit.SECONDS));
238                 mLayoutLatch = new CountDownLatch(1);
239             } catch (InterruptedException e) {
240                 throw new AssertionError(e);
241             }
242         }
243     }
244 }
245