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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 22 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 23 import static android.server.wm.WindowManagerState.STATE_PAUSED; 24 import static android.server.wm.WindowMetricsTestHelper.assertBoundsMatchDisplay; 25 import static android.server.wm.WindowMetricsTestHelper.getBoundsExcludingNavigationBarAndCutout; 26 import static android.server.wm.WindowMetricsTestHelper.maxWindowBoundsSandboxed; 27 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 28 29 import static org.junit.Assert.assertEquals; 30 import static org.junit.Assert.assertTrue; 31 import static org.junit.Assert.fail; 32 import static org.junit.Assume.assumeTrue; 33 34 import android.app.Activity; 35 import android.app.PictureInPictureParams; 36 import android.content.ComponentName; 37 import android.graphics.Point; 38 import android.graphics.Rect; 39 import android.os.Bundle; 40 import android.platform.test.annotations.Presubmit; 41 import android.server.wm.WindowMetricsTestHelper.OnLayoutChangeListener; 42 import android.view.Display; 43 import android.view.WindowManager; 44 import android.view.WindowMetrics; 45 46 import androidx.test.filters.FlakyTest; 47 48 import org.junit.Test; 49 50 /** 51 * Tests that verify the behavior of {@link WindowMetrics} APIs on {@link Activity activities}. 52 * 53 * Build/Install/Run: 54 * atest CtsWindowManagerDeviceTestCases:WindowMetricsActivityTests 55 */ 56 @Presubmit 57 public class WindowMetricsActivityTests extends WindowManagerTestBase { 58 private static final Rect WINDOW_BOUNDS = new Rect(100, 100, 400, 400); 59 private static final Rect RESIZED_WINDOW_BOUNDS = new Rect(100, 100, 900, 900); 60 private static final int MOVE_OFFSET = 100; 61 62 @Test testMetricsMatchesLayoutOnActivityOnCreate()63 public void testMetricsMatchesLayoutOnActivityOnCreate() { 64 final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class, 65 WINDOWING_MODE_FULLSCREEN); 66 final OnLayoutChangeListener listener = activity.mListener; 67 68 listener.waitForLayout(); 69 70 WindowMetricsTestHelper.assertMetricsMatchesLayout(activity.mOnCreateCurrentMetrics, 71 activity.mOnCreateMaximumMetrics, listener.getLayoutBounds(), 72 listener.getLayoutInsets()); 73 } 74 75 @Test testMetricsMatchesDisplayAreaOnActivity()76 public void testMetricsMatchesDisplayAreaOnActivity() { 77 final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class, 78 WINDOWING_MODE_FULLSCREEN); 79 80 assertMetricsValidity(activity); 81 } 82 83 @Test 84 @FlakyTest(bugId = 188207199) testMetricsMatchesActivityBoundsOnNonresizableActivity()85 public void testMetricsMatchesActivityBoundsOnNonresizableActivity() { 86 assumeTrue("Skipping test: no rotation support", supportsRotation()); 87 88 final MinAspectRatioActivity activity = startActivityInWindowingMode( 89 MinAspectRatioActivity.class, WINDOWING_MODE_FULLSCREEN); 90 mWmState.computeState(activity.getComponentName()); 91 92 assertMetricsValidityForNonresizableActivity(activity); 93 } 94 95 @Test testMetricsMatchesLayoutOnPipActivity()96 public void testMetricsMatchesLayoutOnPipActivity() { 97 assumeTrue(supportsPip()); 98 99 final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class, 100 WINDOWING_MODE_FULLSCREEN); 101 102 assertMetricsMatchesLayout(activity); 103 104 activity.enterPictureInPictureMode(new PictureInPictureParams.Builder().build()); 105 waitForEnterPipAnimationComplete(activity.getComponentName()); 106 107 assertMetricsMatchesLayout(activity); 108 } 109 110 @Test testMetricsMatchesDisplayAreaOnPipActivity()111 public void testMetricsMatchesDisplayAreaOnPipActivity() { 112 assumeTrue(supportsPip()); 113 114 final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class, 115 WINDOWING_MODE_FULLSCREEN); 116 117 assertMetricsValidity(activity); 118 119 activity.enterPictureInPictureMode(new PictureInPictureParams.Builder().build()); 120 waitForEnterPipAnimationComplete(activity.getComponentName()); 121 122 assertMetricsValidity(activity); 123 } 124 125 /** 126 * Waits until the picture-in-picture animation has finished. 127 */ waitForEnterPipAnimationComplete(ComponentName activityName)128 private void waitForEnterPipAnimationComplete(ComponentName activityName) { 129 waitForEnterPip(activityName); 130 mWmState.waitForWithAmState(wmState -> { 131 WindowManagerState.Task task = wmState.getTaskByActivity(activityName); 132 if (task == null) { 133 return false; 134 } 135 WindowManagerState.Activity activity = task.getActivity(activityName); 136 return activity.getWindowingMode() == WINDOWING_MODE_PINNED 137 && activity.getState().equals(STATE_PAUSED); 138 }, "checking activity windowing mode"); 139 } 140 141 /** 142 * Waits until the given activity has entered picture-in-picture mode (allowing for the 143 * subsequent animation to start). 144 */ waitForEnterPip(ComponentName activityName)145 private void waitForEnterPip(ComponentName activityName) { 146 mWmState.waitForWithAmState(wmState -> { 147 WindowManagerState.Task task = wmState.getTaskByActivity(activityName); 148 return task != null && task.getWindowingMode() == WINDOWING_MODE_PINNED; 149 }, "checking task windowing mode"); 150 } 151 152 @Test testMetricsMatchesLayoutOnSplitActivity()153 public void testMetricsMatchesLayoutOnSplitActivity() { 154 assumeTrue(supportsSplitScreenMultiWindow()); 155 156 final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class, 157 WINDOWING_MODE_FULLSCREEN); 158 159 assertMetricsMatchesLayout(activity); 160 161 mWmState.computeState(activity.getComponentName()); 162 putActivityInPrimarySplit(activity.getComponentName()); 163 164 mWmState.computeState(activity.getComponentName()); 165 assertTrue(mWmState.getActivity(activity.getComponentName()).getWindowingMode() 166 == WINDOWING_MODE_MULTI_WINDOW); 167 168 assertMetricsMatchesLayout(activity); 169 } 170 171 @Test testMetricsMatchesDisplayAreaOnSplitActivity()172 public void testMetricsMatchesDisplayAreaOnSplitActivity() { 173 assumeTrue(supportsSplitScreenMultiWindow()); 174 175 final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class, 176 WINDOWING_MODE_FULLSCREEN); 177 178 assertMetricsValidity(activity); 179 180 mWmState.computeState(activity.getComponentName()); 181 putActivityInPrimarySplit(activity.getComponentName()); 182 183 mWmState.computeState(activity.getComponentName()); 184 assertTrue(mWmState.getActivity(activity.getComponentName()).getWindowingMode() 185 == WINDOWING_MODE_MULTI_WINDOW); 186 187 assertMetricsValidity(activity); 188 } 189 190 @Test testMetricsMatchesLayoutOnFreeformActivity()191 public void testMetricsMatchesLayoutOnFreeformActivity() { 192 assumeTrue(supportsFreeform()); 193 194 final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class, 195 WINDOWING_MODE_FULLSCREEN); 196 197 assertMetricsMatchesLayout(activity); 198 199 launchActivity(new ComponentName(mTargetContext, MetricsActivity.class), 200 WINDOWING_MODE_FREEFORM); 201 202 // Resize the freeform activity. 203 resizeActivityTask(activity.getComponentName(), WINDOW_BOUNDS.left, WINDOW_BOUNDS.top, 204 WINDOW_BOUNDS.right, WINDOW_BOUNDS.bottom); 205 mWmState.computeState(activity.getComponentName()); 206 207 assertMetricsMatchesLayout(activity); 208 209 // Resize again. 210 resizeActivityTask(activity.getComponentName(), RESIZED_WINDOW_BOUNDS.left, 211 RESIZED_WINDOW_BOUNDS.top, RESIZED_WINDOW_BOUNDS.right, 212 RESIZED_WINDOW_BOUNDS.bottom); 213 mWmState.computeState(activity.getComponentName()); 214 215 assertMetricsMatchesLayout(activity); 216 217 // Move the activity. 218 resizeActivityTask(activity.getComponentName(), MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.left, 219 MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.top, MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.right, 220 MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.bottom); 221 mWmState.computeState(activity.getComponentName()); 222 223 assertMetricsMatchesLayout(activity); 224 } 225 226 @Test testMetricsMatchesDisplayAreaOnFreeformActivity()227 public void testMetricsMatchesDisplayAreaOnFreeformActivity() { 228 assumeTrue(supportsFreeform()); 229 230 final MetricsActivity activity = startActivityInWindowingMode(MetricsActivity.class, 231 WINDOWING_MODE_FULLSCREEN); 232 233 assertMetricsValidity(activity); 234 235 launchActivity(new ComponentName(mTargetContext, MetricsActivity.class), 236 WINDOWING_MODE_FREEFORM); 237 238 // Resize the freeform activity. 239 resizeActivityTask(activity.getComponentName(), WINDOW_BOUNDS.left, WINDOW_BOUNDS.top, 240 WINDOW_BOUNDS.right, WINDOW_BOUNDS.bottom); 241 242 assertMetricsValidity(activity); 243 244 // Resize again. 245 resizeActivityTask(activity.getComponentName(), RESIZED_WINDOW_BOUNDS.left, 246 RESIZED_WINDOW_BOUNDS.top, RESIZED_WINDOW_BOUNDS.right, 247 RESIZED_WINDOW_BOUNDS.bottom); 248 249 assertMetricsValidity(activity); 250 251 // Move the activity. 252 resizeActivityTask(activity.getComponentName(), MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.left, 253 MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.top, MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.right, 254 MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.bottom); 255 256 assertMetricsValidity(activity); 257 } 258 assertMetricsMatchesLayout(MetricsActivity activity)259 private static void assertMetricsMatchesLayout(MetricsActivity activity) { 260 final OnLayoutChangeListener listener = activity.mListener; 261 listener.waitForLayout(); 262 263 final WindowMetrics currentMetrics = activity.getWindowManager().getCurrentWindowMetrics(); 264 final WindowMetrics maxMetrics = activity.getWindowManager().getMaximumWindowMetrics(); 265 266 Condition.waitFor(new Condition<>("WindowMetrics must match layout metrics", 267 () -> currentMetrics.getBounds().equals(listener.getLayoutBounds())) 268 .setRetryIntervalMs(500).setRetryLimit(10) 269 .setOnFailure(unused -> fail("WindowMetrics must match layout metrics. Layout" 270 + "bounds is" + listener.getLayoutBounds() + ", while current window" 271 + "metrics is " + currentMetrics.getBounds()))); 272 273 final boolean isFreeForm = activity.getResources().getConfiguration().windowConfiguration 274 .getWindowingMode() == WINDOWING_MODE_FREEFORM; 275 WindowMetricsTestHelper.assertMetricsMatchesLayout(currentMetrics, maxMetrics, 276 listener.getLayoutBounds(), listener.getLayoutInsets(), isFreeForm); 277 } 278 279 /** 280 * Verifies two scenarios for an {@link Activity}. If the activity is freeform, then the bounds 281 * should not include insets for navigation bar and cutout area. 282 * <ul> 283 * <li>{@link WindowManager#getCurrentWindowMetrics()} matches 284 * {@link Display#getSize(Point)}</li> 285 * <li>{@link WindowManager#getMaximumWindowMetrics()} and {@link Display#getSize(Point)} 286 * either matches DisplayArea bounds which the {@link Activity} is attached to, or matches 287 * {@link WindowManager#getCurrentWindowMetrics()} if sandboxing is applied.</li> 288 * </ul> 289 */ assertMetricsValidity(Activity activity)290 private void assertMetricsValidity(Activity activity) { 291 mWmState.computeState(activity.getComponentName()); 292 WindowMetricsTestHelper.assertMetricsValidity(activity, 293 getTaskDisplayAreaBounds(activity.getComponentName())); 294 } 295 296 /** 297 * Verifies two scenarios for a non-resizable {@link Activity}. Similar to 298 * {@link #assertMetricsValidity(Activity)}, verifies the values of window metrics against 299 * Display size. {@link WindowManager#getMaximumWindowMetrics()} must match activity bounds 300 * if the activity is sandboxed. 301 * 302 * App bounds calculation of a nonresizable activity depends on the orientation of the display 303 * and the app, and the display and activity bounds. Directly compare maximum WindowMetrics 304 * against the value used for sandboxing, since other ways of accessing activity bounds may 305 * have different insets applied. 306 * 307 * @param activity the activity under test 308 */ assertMetricsValidityForNonresizableActivity(Activity activity)309 private void assertMetricsValidityForNonresizableActivity(Activity activity) { 310 ComponentName activityName = activity.getComponentName(); 311 mWmState.computeState(activityName); 312 WindowManagerState.Activity activityContainer = mWmState.getActivity(activityName); 313 final boolean boundsShouldIncludeInsets = 314 activity.getResources().getConfiguration().windowConfiguration 315 .getWindowingMode() == WINDOWING_MODE_FREEFORM 316 || activityContainer.inSizeCompatMode(); 317 final WindowManager windowManager = activity.getWindowManager(); 318 final WindowMetrics currentMetrics = windowManager.getCurrentWindowMetrics(); 319 final Rect currentBounds = boundsShouldIncludeInsets ? currentMetrics.getBounds() 320 : getBoundsExcludingNavigationBarAndCutout(currentMetrics); 321 final Rect maxBounds = windowManager.getMaximumWindowMetrics().getBounds(); 322 final Display display = activity.getDisplay(); 323 324 assertBoundsMatchDisplay(maxBounds, currentBounds, display); 325 326 // Max window bounds should match either DisplayArea bounds, or activity bounds if it is 327 // sandboxed. 328 final Rect displayAreaBounds = getTaskDisplayAreaBounds(activityName); 329 final Rect activityBounds = 330 activityContainer.mFullConfiguration.windowConfiguration.getBounds(); 331 if (maxWindowBoundsSandboxed(displayAreaBounds, maxBounds)) { 332 // Max window bounds are sandboxed, so max window bounds should match activity bounds. 333 assertEquals("Maximum window metrics of a non-resizable activity matches " 334 + "activity bounds, when sandboxed", activityBounds, maxBounds); 335 } else { 336 // Max window bounds are not sandboxed, so max window bounds should match display area 337 // bounds. 338 assertEquals("Display area bounds must match max window size", displayAreaBounds, 339 maxBounds); 340 } 341 } 342 getTaskDisplayAreaBounds(ComponentName activityName)343 private Rect getTaskDisplayAreaBounds(ComponentName activityName) { 344 WindowManagerState.DisplayArea tda = mWmState.getTaskDisplayArea(activityName); 345 return tda.mFullConfiguration.windowConfiguration.getBounds(); 346 } 347 348 public static class MetricsActivity extends FocusableActivity { 349 private WindowMetrics mOnCreateMaximumMetrics; 350 private WindowMetrics mOnCreateCurrentMetrics; 351 private final OnLayoutChangeListener mListener = new OnLayoutChangeListener(); 352 353 @Override onCreate(Bundle savedInstanceState)354 protected void onCreate(Bundle savedInstanceState) { 355 super.onCreate(savedInstanceState); 356 mOnCreateCurrentMetrics = getWindowManager().getCurrentWindowMetrics(); 357 mOnCreateMaximumMetrics = getWindowManager().getMaximumWindowMetrics(); 358 getWindow().getDecorView().addOnLayoutChangeListener(mListener); 359 360 // Always extend the cutout areas because layout doesn't get the waterfall cutout. 361 final WindowManager.LayoutParams attrs = getWindow().getAttributes(); 362 attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 363 getWindow().setAttributes(attrs); 364 } 365 366 @Override onDestroy()367 protected void onDestroy() { 368 super.onDestroy(); 369 getWindow().getDecorView().removeOnLayoutChangeListener(mListener); 370 } 371 } 372 373 public static class MinAspectRatioActivity extends MetricsActivity { 374 } 375 } 376