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