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