blob: fe7ded2ed3d8b10817b26e81cbaaf18f552d6244 [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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.server.wm.WindowManagerState.STATE_PAUSED;
import static android.server.wm.WindowMetricsTestHelper.assertBoundsMatchDisplay;
import static android.server.wm.WindowMetricsTestHelper.getBoundsExcludingNavigationBarAndCutout;
import static android.server.wm.WindowMetricsTestHelper.maxWindowBoundsSandboxed;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
import android.server.wm.WindowMetricsTestHelper.OnLayoutChangeListener;
import android.view.Display;
import android.view.WindowManager;
import android.view.WindowMetrics;
import androidx.test.filters.FlakyTest;
import org.junit.Test;
import java.util.function.Supplier;
/**
* Tests that verify the behavior of {@link WindowMetrics} APIs on {@link Activity activities}.
*
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:WindowMetricsActivityTests
*/
@Presubmit
public class WindowMetricsActivityTests extends WindowManagerTestBase {
private static final Rect WINDOW_BOUNDS = new Rect(100, 100, 400, 400);
private static final Rect RESIZED_WINDOW_BOUNDS = new Rect(100, 100, 900, 900);
private static final int MOVE_OFFSET = 100;
@Test
public void testMetricsMatchesLayoutOnActivityOnCreate() {
final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
WINDOWING_MODE_FULLSCREEN);
final OnLayoutChangeListener listener = activity.mListener;
listener.waitForLayout();
WindowMetricsTestHelper.assertMetricsMatchesLayout(activity.mOnCreateCurrentMetrics,
activity.mOnCreateMaximumMetrics, listener.getLayoutBounds(),
listener.getLayoutInsets());
}
@Test
public void testMetricsMatchesDisplayAreaOnActivity() {
final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
WINDOWING_MODE_FULLSCREEN);
assertMetricsValidity(activity);
}
@Test
@FlakyTest(bugId = 188207199)
public void testMetricsMatchesActivityBoundsOnNonresizableActivity() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
final MinAspectRatioActivity activity = startActivityInWindowingMode(
MinAspectRatioActivity.class, WINDOWING_MODE_FULLSCREEN);
mWmState.computeState(activity.getComponentName());
assertMetricsValidityForNonresizableActivity(activity);
}
@Test
public void testMetricsMatchesLayoutOnPipActivity() {
assumeTrue(supportsPip());
final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
WINDOWING_MODE_FULLSCREEN);
assertMetricsMatchesLayout(activity);
activity.enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
waitForEnterPipAnimationComplete(activity.getComponentName());
assertMetricsMatchesLayout(activity);
}
@Test
public void testMetricsMatchesDisplayAreaOnPipActivity() {
assumeTrue(supportsPip());
final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
WINDOWING_MODE_FULLSCREEN);
assertMetricsValidity(activity);
activity.enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
waitForEnterPipAnimationComplete(activity.getComponentName());
assertMetricsValidity(activity);
}
/**
* Waits until the picture-in-picture animation has finished.
*/
private void waitForEnterPipAnimationComplete(ComponentName activityName) {
waitForEnterPip(activityName);
mWmState.waitForWithAmState(wmState -> {
WindowManagerState.Task task = wmState.getTaskByActivity(activityName);
if (task == null) {
return false;
}
WindowManagerState.Activity activity = task.getActivity(activityName);
return activity.getWindowingMode() == WINDOWING_MODE_PINNED
&& activity.getState().equals(STATE_PAUSED);
}, "checking activity windowing mode");
}
/**
* Waits until the given activity has entered picture-in-picture mode (allowing for the
* subsequent animation to start).
*/
private void waitForEnterPip(ComponentName activityName) {
mWmState.waitForWithAmState(wmState -> {
WindowManagerState.Task task = wmState.getTaskByActivity(activityName);
return task != null && task.getWindowingMode() == WINDOWING_MODE_PINNED;
}, "checking task windowing mode");
}
@Test
public void testMetricsMatchesLayoutOnSplitActivity() {
assumeTrue(supportsSplitScreenMultiWindow());
final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
WINDOWING_MODE_FULLSCREEN);
assertMetricsMatchesLayout(activity);
mWmState.computeState(activity.getComponentName());
putActivityInPrimarySplit(activity.getComponentName());
mWmState.computeState(activity.getComponentName());
assertTrue(mWmState.getActivity(activity.getComponentName()).getWindowingMode()
== WINDOWING_MODE_MULTI_WINDOW);
assertMetricsMatchesLayout(activity);
}
@Test
public void testMetricsMatchesDisplayAreaOnSplitActivity() {
assumeTrue(supportsSplitScreenMultiWindow());
final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
WINDOWING_MODE_FULLSCREEN);
assertMetricsValidity(activity);
mWmState.computeState(activity.getComponentName());
putActivityInPrimarySplit(activity.getComponentName());
mWmState.computeState(activity.getComponentName());
assertTrue(mWmState.getActivity(activity.getComponentName()).getWindowingMode()
== WINDOWING_MODE_MULTI_WINDOW);
assertMetricsValidity(activity);
}
@Test
public void testMetricsMatchesLayoutOnFreeformActivity() {
assumeTrue(supportsFreeform());
final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
WINDOWING_MODE_FULLSCREEN);
assertMetricsMatchesLayout(activity);
launchActivity(new ComponentName(mTargetContext, MetricsActivity.class),
WINDOWING_MODE_FREEFORM);
// Resize the freeform activity.
resizeActivityTask(activity.getComponentName(), WINDOW_BOUNDS.left, WINDOW_BOUNDS.top,
WINDOW_BOUNDS.right, WINDOW_BOUNDS.bottom);
assertMetricsMatchesLayout(activity);
// Resize again.
resizeActivityTask(activity.getComponentName(), RESIZED_WINDOW_BOUNDS.left,
RESIZED_WINDOW_BOUNDS.top, RESIZED_WINDOW_BOUNDS.right,
RESIZED_WINDOW_BOUNDS.bottom);
assertMetricsMatchesLayout(activity);
// Move the activity.
resizeActivityTask(activity.getComponentName(), MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.left,
MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.top, MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.right,
MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.bottom);
assertMetricsMatchesLayout(activity);
}
@Test
public void testMetricsMatchesDisplayAreaOnFreeformActivity() {
assumeTrue(supportsFreeform());
final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class,
WINDOWING_MODE_FULLSCREEN);
assertMetricsValidity(activity);
launchActivity(new ComponentName(mTargetContext, MetricsActivity.class),
WINDOWING_MODE_FREEFORM);
// Resize the freeform activity.
resizeActivityTask(activity.getComponentName(), WINDOW_BOUNDS.left, WINDOW_BOUNDS.top,
WINDOW_BOUNDS.right, WINDOW_BOUNDS.bottom);
assertMetricsValidity(activity);
// Resize again.
resizeActivityTask(activity.getComponentName(), RESIZED_WINDOW_BOUNDS.left,
RESIZED_WINDOW_BOUNDS.top, RESIZED_WINDOW_BOUNDS.right,
RESIZED_WINDOW_BOUNDS.bottom);
assertMetricsValidity(activity);
// Move the activity.
resizeActivityTask(activity.getComponentName(), MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.left,
MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.top, MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.right,
MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.bottom);
assertMetricsValidity(activity);
}
private static void assertMetricsMatchesLayout(MetricsActivity activity) {
final OnLayoutChangeListener listener = activity.mListener;
listener.waitForLayout();
final Supplier<WindowMetrics> currentMetrics =
() -> activity.getWindowManager().getCurrentWindowMetrics();
final Supplier<WindowMetrics> maxMetrics =
() -> activity.getWindowManager().getMaximumWindowMetrics();
Condition.waitFor(new Condition<>("WindowMetrics must match layout metrics",
() -> currentMetrics.get().getBounds().equals(listener.getLayoutBounds()))
.setRetryIntervalMs(500).setRetryLimit(10)
.setOnFailure(unused -> fail("WindowMetrics must match layout metrics. Layout"
+ "bounds is" + listener.getLayoutBounds() + ", while current window"
+ "metrics is " + currentMetrics.get().getBounds())));
final boolean isFreeForm = activity.getResources().getConfiguration().windowConfiguration
.getWindowingMode() == WINDOWING_MODE_FREEFORM;
WindowMetricsTestHelper.assertMetricsMatchesLayout(currentMetrics.get(), maxMetrics.get(),
listener.getLayoutBounds(), listener.getLayoutInsets(), isFreeForm);
}
/**
* Verifies two scenarios for an {@link Activity}. If the activity is freeform, then the bounds
* should not include insets for navigation bar and cutout area.
* <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 Activity} is attached to, or matches
* {@link WindowManager#getCurrentWindowMetrics()} if sandboxing is applied.</li>
* </ul>
*/
private void assertMetricsValidity(Activity activity) {
mWmState.computeState(activity.getComponentName());
WindowMetricsTestHelper.assertMetricsValidity(activity,
getTaskDisplayAreaBounds(activity.getComponentName()));
}
/**
* Verifies two scenarios for a non-resizable {@link Activity}. Similar to
* {@link #assertMetricsValidity(Activity)}, verifies the values of window metrics against
* Display size. {@link WindowManager#getMaximumWindowMetrics()} must match activity bounds
* if the activity is sandboxed.
*
* App bounds calculation of a nonresizable activity depends on the orientation of the display
* and the app, and the display and activity bounds. Directly compare maximum WindowMetrics
* against the value used for sandboxing, since other ways of accessing activity bounds may
* have different insets applied.
*
* @param activity the activity under test
*/
private void assertMetricsValidityForNonresizableActivity(Activity activity) {
ComponentName activityName = activity.getComponentName();
mWmState.computeState(activityName);
WindowManagerState.Activity activityContainer = mWmState.getActivity(activityName);
final boolean boundsShouldIncludeInsets =
activity.getResources().getConfiguration().windowConfiguration
.getWindowingMode() == WINDOWING_MODE_FREEFORM
|| activityContainer.inSizeCompatMode();
final WindowManager windowManager = activity.getWindowManager();
final WindowMetrics currentMetrics = windowManager.getCurrentWindowMetrics();
final Rect currentBounds = boundsShouldIncludeInsets ? currentMetrics.getBounds()
: getBoundsExcludingNavigationBarAndCutout(currentMetrics);
final Rect maxBounds = windowManager.getMaximumWindowMetrics().getBounds();
final Display display = activity.getDisplay();
assertBoundsMatchDisplay(maxBounds, currentBounds, display);
// Max window bounds should match either DisplayArea bounds, or activity bounds if it is
// sandboxed.
final Rect displayAreaBounds = getTaskDisplayAreaBounds(activityName);
final Rect activityBounds =
activityContainer.mFullConfiguration.windowConfiguration.getBounds();
if (maxWindowBoundsSandboxed(displayAreaBounds, maxBounds)) {
// Max window bounds are sandboxed, so max window bounds should match activity bounds.
assertEquals("Maximum window metrics of a non-resizable activity matches "
+ "activity bounds, when sandboxed", activityBounds, maxBounds);
} else {
// Max window bounds are not sandboxed, so max window bounds should match display area
// bounds.
assertEquals("Display area bounds must match max window size", displayAreaBounds,
maxBounds);
}
}
private Rect getTaskDisplayAreaBounds(ComponentName activityName) {
WindowManagerState.DisplayArea tda = mWmState.getTaskDisplayArea(activityName);
return tda.mFullConfiguration.windowConfiguration.getBounds();
}
public static class MetricsActivity extends FocusableActivity {
private WindowMetrics mOnCreateMaximumMetrics;
private WindowMetrics mOnCreateCurrentMetrics;
private final OnLayoutChangeListener mListener = new OnLayoutChangeListener();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mOnCreateCurrentMetrics = getWindowManager().getCurrentWindowMetrics();
mOnCreateMaximumMetrics = getWindowManager().getMaximumWindowMetrics();
getWindow().getDecorView().addOnLayoutChangeListener(mListener);
// Always extend the cutout areas because layout doesn't get the waterfall cutout.
final WindowManager.LayoutParams attrs = getWindow().getAttributes();
attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
getWindow().setAttributes(attrs);
}
@Override
protected void onDestroy() {
super.onDestroy();
getWindow().getDecorView().removeOnLayoutChangeListener(mListener);
}
}
public static class MinAspectRatioActivity extends MetricsActivity {
}
}