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