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