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.view.Display;
33 import android.view.View;
34 import android.view.WindowInsets;
35 import android.view.WindowManager;
36 import android.view.WindowMetrics;
37 
38 import java.util.concurrent.CountDownLatch;
39 import java.util.concurrent.TimeUnit;
40 
41 /** Helper class to test {@link WindowMetrics} behaviors. */
42 public class WindowMetricsTestHelper {
assertMetricsMatchesLayout(WindowMetrics currentMetrics, WindowMetrics maxMetrics, Rect layoutBounds, WindowInsets layoutInsets)43     public static void assertMetricsMatchesLayout(WindowMetrics currentMetrics,
44             WindowMetrics maxMetrics, Rect layoutBounds, WindowInsets layoutInsets) {
45         assertMetricsMatchesLayout(currentMetrics, maxMetrics, layoutBounds, layoutInsets,
46                 false /* isFreeformActivity */);
47     }
48 
assertMetricsMatchesLayout(WindowMetrics currentMetrics, WindowMetrics maxMetrics, Rect layoutBounds, WindowInsets layoutInsets, boolean isFreeformActivity)49     public static void assertMetricsMatchesLayout(WindowMetrics currentMetrics,
50             WindowMetrics maxMetrics, Rect layoutBounds, WindowInsets layoutInsets,
51             boolean isFreeformActivity) {
52         assertEquals(layoutBounds, currentMetrics.getBounds());
53         // Freeform activities doesn't guarantee max window metrics bounds is larger than current
54         // window metrics bounds. The bounds of a freeform activity is unlimited except that
55         // it must be contained in display bounds.
56         if (!isFreeformActivity) {
57             assertTrue(maxMetrics.getBounds().width()
58                     >= currentMetrics.getBounds().width());
59             assertTrue(maxMetrics.getBounds().height()
60                     >= currentMetrics.getBounds().height());
61         }
62         final int insetsType = statusBars() | navigationBars() | displayCutout();
63         assertEquals(layoutInsets.getInsets(insetsType),
64                 currentMetrics.getWindowInsets().getInsets(insetsType));
65         assertEquals(layoutInsets.getDisplayCutout(),
66                 currentMetrics.getWindowInsets().getDisplayCutout());
67     }
68 
69     /**
70      * Verifies two scenarios for a {@link Context}.
71      * <ul>
72      *     <li>{@link WindowManager#getCurrentWindowMetrics()} matches
73      *     {@link Display#getSize(Point)}</li>
74      *     <li>{@link WindowManager#getMaximumWindowMetrics()} and {@link Display#getSize(Point)}
75      *     either matches DisplayArea bounds which the {@link Context} is attached to, or matches
76      *     {@link WindowManager#getCurrentWindowMetrics()} if sandboxing is applied.</li>
77      * </ul>
78      * @param context the context under test
79      * @param displayAreaBounds the bounds of the DisplayArea
80      */
assertMetricsValidity(Context context, Rect displayAreaBounds)81     public static void assertMetricsValidity(Context context, Rect displayAreaBounds) {
82         final boolean isFreeForm = context instanceof Activity
83                 && context.getResources().getConfiguration().windowConfiguration
84                 .getWindowingMode() == WINDOWING_MODE_FREEFORM;
85         final WindowManager windowManager = context.getSystemService(WindowManager.class);
86         // Freeform activity doesn't inset the navigation bar and cutout area.
87         final Rect currentBounds =
88                 isFreeForm ? windowManager.getCurrentWindowMetrics().getBounds() :
89                         getBoundsExcludingNavigationBarAndCutout(
90                                 windowManager.getCurrentWindowMetrics());
91         final Rect maxBounds = windowManager.getMaximumWindowMetrics().getBounds();
92         final Display display = context.getDisplay();
93         assertBoundsMatchDisplay(maxBounds, currentBounds, display);
94 
95         // Max window bounds should match either DisplayArea bounds, or current window bounds.
96         if (maxWindowBoundsSandboxed(displayAreaBounds, maxBounds)) {
97             // Max window bounds are sandboxed, so max window bounds and real display size
98             // should match current window bounds.
99             assertEquals("Max window size matches current window size, due to sandboxing",
100                     currentBounds, maxBounds);
101         } else {
102             // Max window bounds are not sandboxed, so max window bounds and real display size
103             // should match display area bounds.
104             assertEquals("Display area bounds must match max window size",
105                     displayAreaBounds, maxBounds);
106         }
107     }
108 
109     /**
110      * Verifies for the provided bounds and display:
111      * <ul>
112      *     <li>{@link WindowManager#getCurrentWindowMetrics()} matches
113      *     {@link Display#getSize(Point)}</li>
114      *     <li>{@link WindowManager#getMaximumWindowMetrics()} matches
115      *     {@link Display#getRealSize(Point)}
116      * </ul>
117      * @param maxBounds the bounds from {@link WindowManager#getMaximumWindowMetrics()}
118      * @param currentBounds the bounds from {@link WindowManager#getCurrentWindowMetrics()}
119      * @param display the display to compare bounds against
120      */
assertBoundsMatchDisplay(Rect maxBounds, Rect currentBounds, Display display)121     static void assertBoundsMatchDisplay(Rect maxBounds, Rect currentBounds, Display display) {
122         // Check window bounds
123         final Point displaySize = new Point();
124         display.getSize(displaySize);
125         assertEquals("Reported display width must match window width",
126                 displaySize.x, currentBounds.width());
127         assertEquals("Reported display height must match window height",
128                 displaySize.y, currentBounds.height());
129 
130         // Max window bounds should match real display size.
131         final Point realDisplaySize = new Point();
132         display.getRealSize(realDisplaySize);
133         assertEquals("Reported real display width must match max window width",
134                 realDisplaySize.x, maxBounds.width());
135         assertEquals("Reported real display height must match max window height",
136                 realDisplaySize.y, maxBounds.height());
137     }
138 
getBoundsExcludingNavigationBarAndCutout(WindowMetrics windowMetrics)139     public static Rect getBoundsExcludingNavigationBarAndCutout(WindowMetrics windowMetrics) {
140         WindowInsets windowInsets = windowMetrics.getWindowInsets();
141         final Insets insetsWithCutout =
142                 windowInsets.getInsetsIgnoringVisibility(navigationBars() | displayCutout());
143 
144         final Rect bounds = windowMetrics.getBounds();
145         return inset(bounds, insetsWithCutout);
146     }
147 
148     /**
149      * Returns {@code true} if the bounds from {@link WindowManager#getMaximumWindowMetrics()} are
150      * sandboxed, so are smaller than the DisplayArea.
151      */
maxWindowBoundsSandboxed(Rect displayAreaBounds, Rect maxBounds)152     static boolean maxWindowBoundsSandboxed(Rect displayAreaBounds, Rect maxBounds) {
153         return maxBounds.width() < displayAreaBounds.width()
154                 || maxBounds.height() < displayAreaBounds.height();
155     }
156 
inset(Rect original, Insets insets)157     private static Rect inset(Rect original, Insets insets) {
158         final int left = original.left + insets.left;
159         final int top = original.top + insets.top;
160         final int right = original.right - insets.right;
161         final int bottom = original.bottom - insets.bottom;
162         return new Rect(left, top, right, bottom);
163     }
164 
165     public static class OnLayoutChangeListener implements View.OnLayoutChangeListener {
166         private final CountDownLatch mLayoutLatch = new CountDownLatch(1);
167 
168         private volatile Rect mOnLayoutBoundsInScreen;
169         private volatile WindowInsets mOnLayoutInsets;
170 
171         @Override
onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)172         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
173                 int oldTop, int oldRight, int oldBottom) {
174             synchronized (this) {
175                 mOnLayoutBoundsInScreen = new Rect(left, top, right, bottom);
176                 // Convert decorView's bounds from window coordinates to screen coordinates.
177                 final int[] locationOnScreen = new int[2];
178                 v.getLocationOnScreen(locationOnScreen);
179                 mOnLayoutBoundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
180 
181                 mOnLayoutInsets = v.getRootWindowInsets();
182                 mLayoutLatch.countDown();
183             }
184         }
185 
getLayoutBounds()186         public Rect getLayoutBounds() {
187             synchronized (this) {
188                 return mOnLayoutBoundsInScreen;
189             }
190         }
191 
getLayoutInsets()192         public WindowInsets getLayoutInsets() {
193             synchronized (this) {
194                 return mOnLayoutInsets;
195             }
196         }
197 
waitForLayout()198         void waitForLayout() {
199             try {
200                 assertTrue("Timed out waiting for layout.",
201                         mLayoutLatch.await(4, TimeUnit.SECONDS));
202             } catch (InterruptedException e) {
203                 throw new AssertionError(e);
204             }
205         }
206     }
207 }
208