blob: 6fbfe36c75e8541b345126cdb3690fd249f33c99 [file] [log] [blame]
/*
* 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}.
* <ul>
* <li>{@link WindowManager#getCurrentWindowMetrics()} matches
* {@link Display#getSize(Point)}</li>
* <li>{@link WindowManager#getMaximumWindowMetrics()} and {@link Display#getSize(Point)}
* either matches DisplayArea bounds which the {@link Context} is attached to, or matches
* {@link WindowManager#getCurrentWindowMetrics()} if sandboxing is applied.</li>
* </ul>
* @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:
* <ul>
* <li>{@link WindowManager#getCurrentWindowMetrics()} matches
* {@link Display#getSize(Point)}</li>
* <li>{@link WindowManager#getMaximumWindowMetrics()} matches
* {@link Display#getRealSize(Point)}
* </ul>
* @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);
}
}
}
}