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