• 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.server.wm.ActivityManagerTestBase.isTablet;
21 import static android.view.WindowInsets.Type.displayCutout;
22 import static android.view.WindowInsets.Type.navigationBars;
23 import static android.view.WindowInsets.Type.statusBars;
24 
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertTrue;
27 
28 import android.app.Activity;
29 import android.content.Context;
30 import android.graphics.Insets;
31 import android.graphics.Point;
32 import android.graphics.Rect;
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         // Freeform activity doesn't inset the navigation bar and cutout area.
98         final Rect currentBounds =
99                 isFreeForm ? windowManager.getCurrentWindowMetrics().getBounds() :
100                         getBoundsExcludingNavigationBarAndCutout(
101                                 windowManager.getCurrentWindowMetrics());
102         final Rect maxBounds = windowManager.getMaximumWindowMetrics().getBounds();
103         final Display display = context.getDisplay();
104         assertBoundsMatchDisplay(maxBounds, currentBounds, display);
105 
106         // Max window bounds should match either DisplayArea bounds, or current window bounds.
107         if (maxWindowBoundsSandboxed(displayAreaBounds, maxBounds)) {
108             // Max window bounds are sandboxed, so max window bounds and real display size
109             // should match current window bounds.
110             assertEquals("Max window size matches current window size, due to sandboxing",
111                     currentBounds, maxBounds);
112         } else {
113             // Max window bounds are not sandboxed, so max window bounds and real display size
114             // should match display area bounds.
115             assertEquals("Display area bounds must match max window size",
116                     displayAreaBounds, maxBounds);
117         }
118     }
119 
120     /**
121      * Verifies for the provided bounds and display:
122      * <ul>
123      *     <li>{@link WindowManager#getCurrentWindowMetrics()} matches
124      *     {@link Display#getSize(Point)}</li>
125      *     <li>{@link WindowManager#getMaximumWindowMetrics()} matches
126      *     {@link Display#getRealSize(Point)}
127      * </ul>
128      * @param maxBounds the bounds from {@link WindowManager#getMaximumWindowMetrics()}
129      * @param currentBounds the bounds from {@link WindowManager#getCurrentWindowMetrics()}
130      * @param display the display to compare bounds against
131      */
assertBoundsMatchDisplay(Rect maxBounds, Rect currentBounds, Display display)132     static void assertBoundsMatchDisplay(Rect maxBounds, Rect currentBounds, Display display) {
133         // TODO(b/224404595): remove the logic after we can revert ag/17076728 back.
134         if (isTablet()) {
135             return;
136         }
137 
138         // Check window bounds
139         final Point displaySize = new Point();
140         display.getSize(displaySize);
141         assertEquals("Reported display width must match window width",
142                 displaySize.x, currentBounds.width());
143         assertEquals("Reported display height must match window height",
144                 displaySize.y, currentBounds.height());
145 
146         // Max window bounds should match real display size.
147         final Point realDisplaySize = new Point();
148         display.getRealSize(realDisplaySize);
149         assertEquals("Reported real display width must match max window width",
150                 realDisplaySize.x, maxBounds.width());
151         assertEquals("Reported real display height must match max window height",
152                 realDisplaySize.y, maxBounds.height());
153     }
154 
getBoundsExcludingNavigationBarAndCutout(WindowMetrics windowMetrics)155     public static Rect getBoundsExcludingNavigationBarAndCutout(WindowMetrics windowMetrics) {
156         WindowInsets windowInsets = windowMetrics.getWindowInsets();
157         final Insets insetsWithCutout =
158                 windowInsets.getInsetsIgnoringVisibility(navigationBars() | displayCutout());
159 
160         final Rect bounds = windowMetrics.getBounds();
161         return inset(bounds, insetsWithCutout);
162     }
163 
164     /**
165      * Returns {@code true} if the bounds from {@link WindowManager#getMaximumWindowMetrics()} are
166      * sandboxed, so are smaller than the DisplayArea.
167      */
maxWindowBoundsSandboxed(Rect displayAreaBounds, Rect maxBounds)168     static boolean maxWindowBoundsSandboxed(Rect displayAreaBounds, Rect maxBounds) {
169         return maxBounds.width() < displayAreaBounds.width()
170                 || maxBounds.height() < displayAreaBounds.height();
171     }
172 
inset(Rect original, Insets insets)173     private static Rect inset(Rect original, Insets insets) {
174         final int left = original.left + insets.left;
175         final int top = original.top + insets.top;
176         final int right = original.right - insets.right;
177         final int bottom = original.bottom - insets.bottom;
178         return new Rect(left, top, right, bottom);
179     }
180 
181     public static class OnLayoutChangeListener implements View.OnLayoutChangeListener {
182         private final CountDownLatch mLayoutLatch = new CountDownLatch(1);
183 
184         private volatile Rect mOnLayoutBoundsInScreen;
185         private volatile WindowInsets mOnLayoutInsets;
186 
187         @Override
onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)188         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
189                 int oldTop, int oldRight, int oldBottom) {
190             synchronized (this) {
191                 mOnLayoutBoundsInScreen = new Rect(left, top, right, bottom);
192                 // Convert decorView's bounds from window coordinates to screen coordinates.
193                 final int[] locationOnScreen = new int[2];
194                 v.getLocationOnScreen(locationOnScreen);
195                 mOnLayoutBoundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
196 
197                 mOnLayoutInsets = v.getRootWindowInsets();
198                 mLayoutLatch.countDown();
199             }
200         }
201 
getLayoutBounds()202         public Rect getLayoutBounds() {
203             synchronized (this) {
204                 return mOnLayoutBoundsInScreen;
205             }
206         }
207 
getLayoutInsets()208         public WindowInsets getLayoutInsets() {
209             synchronized (this) {
210                 return mOnLayoutInsets;
211             }
212         }
213 
waitForLayout()214         void waitForLayout() {
215             try {
216                 assertTrue("Timed out waiting for layout.",
217                         mLayoutLatch.await(4, TimeUnit.SECONDS));
218             } catch (InterruptedException e) {
219                 throw new AssertionError(e);
220             }
221         }
222     }
223 }
224