blob: 0a9871f03617c60cada28af3f90c4e6393899a7e [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.getBoundsExcludingNavigationBarAndCutout;
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 org.junit.Test;
/**
* 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
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.ActivityTask 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.ActivityTask 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);
mWmState.computeState(activity.getComponentName());
assertMetricsMatchesLayout(activity);
// Resize again.
resizeActivityTask(activity.getComponentName(), RESIZED_WINDOW_BOUNDS.left,
RESIZED_WINDOW_BOUNDS.top, RESIZED_WINDOW_BOUNDS.right,
RESIZED_WINDOW_BOUNDS.bottom);
mWmState.computeState(activity.getComponentName());
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);
mWmState.computeState(activity.getComponentName());
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 WindowMetrics currentMetrics = activity.getWindowManager().getCurrentWindowMetrics();
final WindowMetrics maxMetrics = activity.getWindowManager().getMaximumWindowMetrics();
Condition.waitFor(new Condition<>("WindowMetrics must match layout metrics",
() -> currentMetrics.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.getBounds())));
final boolean isFreeForm = activity.getResources().getConfiguration().windowConfiguration
.getWindowingMode() == WINDOWING_MODE_FREEFORM;
WindowMetricsTestHelper.assertMetricsMatchesLayout(currentMetrics, maxMetrics,
listener.getLayoutBounds(), listener.getLayoutInsets(), isFreeForm);
}
/**
* Verify two scenarios for an {@link Activity}
* <ul>
* <li>{@link WindowManager#getCurrentWindowMetrics()} matches
* {@link Display#getSize(Point)}</li>
* <li>{@link WindowManager#getMaximumWindowMetrics()} matches
* DisplayArea bounds which the {@link Activity} is attached to.</li>
* </ul>
*/
private void assertMetricsValidity(Activity activity) {
mWmState.computeState(activity.getComponentName());
final Display display = activity.getDisplay();
// Check window bounds
final Point displaySize = new Point();
final boolean isFreeForm = activity.getResources().getConfiguration().windowConfiguration
.getWindowingMode() == WINDOWING_MODE_FREEFORM;
display.getSize(displaySize);
final WindowMetrics windowMetrics = activity.getWindowManager().getCurrentWindowMetrics();
// Freeform activity doesn't inset the navigation bar and cutout area.
final Rect bounds = isFreeForm ? windowMetrics.getBounds() :
getBoundsExcludingNavigationBarAndCutout(windowMetrics);
assertEquals("Reported display width must match window width",
displaySize.x, bounds.width());
assertEquals("Reported display height must match window height",
displaySize.y, bounds.height());
// Check max window bounds
final Rect tdaBounds = getTaskDisplayAreaBounds(activity.getComponentName());
final WindowMetrics maxWindowMetrics = activity.getWindowManager()
.getMaximumWindowMetrics();
assertEquals("Display area bounds must match max window size",
tdaBounds, maxWindowMetrics.getBounds());
}
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);
}
}
}