/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import android.app.Activity; import android.content.Context; import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; import android.view.Display; import android.view.View; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowMetrics; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** Helper class to test {@link WindowMetrics} behaviors. */ public class WindowMetricsTestHelper { public static void assertMetricsMatchesLayout(WindowMetrics currentMetrics, WindowMetrics maxMetrics, Rect layoutBounds, WindowInsets layoutInsets) { assertMetricsMatchesLayout(currentMetrics, maxMetrics, layoutBounds, layoutInsets, false /* isFreeformActivity */); } public static void assertMetricsMatchesLayout(WindowMetrics currentMetrics, WindowMetrics maxMetrics, Rect layoutBounds, WindowInsets layoutInsets, boolean isFreeformActivity) { assertEquals(layoutBounds, currentMetrics.getBounds()); // Freeform activities doesn't guarantee max window metrics bounds is larger than current // window metrics bounds. The bounds of a freeform activity is unlimited except that // it must be contained in display bounds. if (!isFreeformActivity) { assertTrue(maxMetrics.getBounds().width() >= currentMetrics.getBounds().width()); assertTrue(maxMetrics.getBounds().height() >= currentMetrics.getBounds().height()); } final int insetsType = statusBars() | navigationBars() | displayCutout(); assertEquals(layoutInsets.getInsets(insetsType), currentMetrics.getWindowInsets().getInsets(insetsType)); assertEquals(layoutInsets.getDisplayCutout(), currentMetrics.getWindowInsets().getDisplayCutout()); } /** * Verifies two scenarios for a {@link Context}. * * @param context the context under test * @param displayAreaBounds the bounds of the DisplayArea */ public static void assertMetricsValidity(Context context, Rect displayAreaBounds) { final boolean isFreeForm = context instanceof Activity && context.getResources().getConfiguration().windowConfiguration .getWindowingMode() == WINDOWING_MODE_FREEFORM; final WindowManager windowManager = context.getSystemService(WindowManager.class); // Freeform activity doesn't inset the navigation bar and cutout area. final Rect currentBounds = isFreeForm ? windowManager.getCurrentWindowMetrics().getBounds() : getBoundsExcludingNavigationBarAndCutout( windowManager.getCurrentWindowMetrics()); final Rect maxBounds = windowManager.getMaximumWindowMetrics().getBounds(); final Display display = context.getDisplay(); assertBoundsMatchDisplay(maxBounds, currentBounds, display); // Max window bounds should match either DisplayArea bounds, or current window bounds. if (maxWindowBoundsSandboxed(displayAreaBounds, maxBounds)) { // Max window bounds are sandboxed, so max window bounds and real display size // should match current window bounds. assertEquals("Max window size matches current window size, due to sandboxing", currentBounds, maxBounds); } else { // Max window bounds are not sandboxed, so max window bounds and real display size // should match display area bounds. assertEquals("Display area bounds must match max window size", displayAreaBounds, maxBounds); } } /** * Verifies for the provided bounds and display: * * @param maxBounds the bounds from {@link WindowManager#getMaximumWindowMetrics()} * @param currentBounds the bounds from {@link WindowManager#getCurrentWindowMetrics()} * @param display the display to compare bounds against */ static void assertBoundsMatchDisplay(Rect maxBounds, Rect currentBounds, Display display) { // Check window bounds final Point displaySize = new Point(); display.getSize(displaySize); assertEquals("Reported display width must match window width", displaySize.x, currentBounds.width()); assertEquals("Reported display height must match window height", displaySize.y, currentBounds.height()); // Max window bounds should match real display size. final Point realDisplaySize = new Point(); display.getRealSize(realDisplaySize); assertEquals("Reported real display width must match max window width", realDisplaySize.x, maxBounds.width()); assertEquals("Reported real display height must match max window height", realDisplaySize.y, maxBounds.height()); } public static Rect getBoundsExcludingNavigationBarAndCutout(WindowMetrics windowMetrics) { WindowInsets windowInsets = windowMetrics.getWindowInsets(); final Insets insetsWithCutout = windowInsets.getInsetsIgnoringVisibility(navigationBars() | displayCutout()); final Rect bounds = windowMetrics.getBounds(); return inset(bounds, insetsWithCutout); } /** * Returns {@code true} if the bounds from {@link WindowManager#getMaximumWindowMetrics()} are * sandboxed, so are smaller than the DisplayArea. */ static boolean maxWindowBoundsSandboxed(Rect displayAreaBounds, Rect maxBounds) { return maxBounds.width() < displayAreaBounds.width() || maxBounds.height() < displayAreaBounds.height(); } private static Rect inset(Rect original, Insets insets) { final int left = original.left + insets.left; final int top = original.top + insets.top; final int right = original.right - insets.right; final int bottom = original.bottom - insets.bottom; return new Rect(left, top, right, bottom); } public static class OnLayoutChangeListener implements View.OnLayoutChangeListener { private final CountDownLatch mLayoutLatch = new CountDownLatch(1); private volatile Rect mOnLayoutBoundsInScreen; private volatile WindowInsets mOnLayoutInsets; @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { synchronized (this) { mOnLayoutBoundsInScreen = new Rect(left, top, right, bottom); // Convert decorView's bounds from window coordinates to screen coordinates. final int[] locationOnScreen = new int[2]; v.getLocationOnScreen(locationOnScreen); mOnLayoutBoundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]); mOnLayoutInsets = v.getRootWindowInsets(); mLayoutLatch.countDown(); } } public Rect getLayoutBounds() { synchronized (this) { return mOnLayoutBoundsInScreen; } } public WindowInsets getLayoutInsets() { synchronized (this) { return mOnLayoutInsets; } } void waitForLayout() { try { assertTrue("Timed out waiting for layout.", mLayoutLatch.await(4, TimeUnit.SECONDS)); } catch (InterruptedException e) { throw new AssertionError(e); } } } }