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