1 /* 2 * Copyright (C) 2023 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.view; 18 19 import static android.view.InsetsSource.ID_IME; 20 import static android.view.WindowInsets.Type.displayCutout; 21 import static android.view.WindowInsets.Type.ime; 22 import static android.view.WindowInsets.Type.navigationBars; 23 import static android.view.WindowInsets.Type.statusBars; 24 import static android.view.WindowLayout.UNSPECIFIED_LENGTH; 25 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 26 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 27 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 28 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; 29 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; 30 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; 31 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME; 32 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; 33 34 import static org.junit.Assert.assertEquals; 35 36 import android.app.WindowConfiguration; 37 import android.graphics.Insets; 38 import android.graphics.Rect; 39 import android.platform.test.annotations.Presubmit; 40 import android.window.ClientWindowFrames; 41 42 import androidx.test.filters.SmallTest; 43 44 import org.junit.Before; 45 import org.junit.Test; 46 47 /** 48 * Tests for the {@link WindowLayout#computeFrames}. 49 * 50 * Build/Install/Run: 51 * atest WmTests:WindowLayoutTests 52 */ 53 @SmallTest 54 @Presubmit 55 public class WindowLayoutTests { 56 private static final int DISPLAY_WIDTH = 500; 57 private static final int DISPLAY_HEIGHT = 1000; 58 private static final int STATUS_BAR_HEIGHT = 10; 59 private static final int NAVIGATION_BAR_HEIGHT = 15; 60 private static final int IME_HEIGHT = 400; 61 private static final int DISPLAY_CUTOUT_HEIGHT = 8; 62 private static final Rect DISPLAY_CUTOUT_BOUNDS_TOP = 63 new Rect(DISPLAY_WIDTH / 4, 0, DISPLAY_WIDTH * 3 / 4, DISPLAY_CUTOUT_HEIGHT); 64 private static final Insets WATERFALL_INSETS = Insets.of(6, 0, 12, 0); 65 66 private static final int ID_STATUS_BAR = InsetsSource.createId( 67 null /* owner */, 0 /* index */, statusBars()); 68 private static final int ID_NAVIGATION_BAR = InsetsSource.createId( 69 null /* owner */, 0 /* index */, navigationBars()); 70 71 private final WindowLayout mWindowLayout = new WindowLayout(); 72 private final ClientWindowFrames mFrames = new ClientWindowFrames(); 73 74 private WindowManager.LayoutParams mAttrs; 75 private InsetsState mState; 76 private final Rect mDisplayCutoutSafe = new Rect(); 77 private Rect mWindowBounds; 78 private int mWindowingMode; 79 private int mRequestedWidth; 80 private int mRequestedHeight; 81 private int mRequestedVisibleTypes; 82 private float mCompatScale; 83 84 @Before setUp()85 public void setUp() { 86 mAttrs = new WindowManager.LayoutParams(); 87 mState = new InsetsState(); 88 mState.setDisplayFrame(new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)); 89 mState.getOrCreateSource(ID_STATUS_BAR, statusBars()).setFrame( 90 0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT); 91 mState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars()).setFrame( 92 0, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT); 93 mState.getDisplayCutoutSafe(mDisplayCutoutSafe); 94 mWindowBounds = new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); 95 mWindowingMode = WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 96 mRequestedWidth = DISPLAY_WIDTH; 97 mRequestedHeight = DISPLAY_HEIGHT; 98 mRequestedVisibleTypes = WindowInsets.Type.defaultVisible(); 99 mCompatScale = 1f; 100 mFrames.attachedFrame = null; 101 } 102 computeFrames()103 private void computeFrames() { 104 mWindowLayout.computeFrames(mAttrs, mState, mDisplayCutoutSafe, mWindowBounds, 105 mWindowingMode, mRequestedWidth, mRequestedHeight, mRequestedVisibleTypes, 106 mCompatScale, mFrames); 107 } 108 addDisplayCutout()109 private void addDisplayCutout() { 110 mState.setDisplayCutout(new DisplayCutout( 111 Insets.of(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0), 112 new Rect(), 113 DISPLAY_CUTOUT_BOUNDS_TOP, 114 new Rect(), 115 new Rect(), 116 WATERFALL_INSETS)); 117 mState.getDisplayCutoutSafe(mDisplayCutoutSafe); 118 mState.getOrCreateSource(InsetsSource.createId(null, 0, displayCutout()), displayCutout()) 119 .setFrame(0, 0, mDisplayCutoutSafe.left, DISPLAY_HEIGHT); 120 mState.getOrCreateSource(InsetsSource.createId(null, 1, displayCutout()), displayCutout()) 121 .setFrame(0, 0, DISPLAY_WIDTH, mDisplayCutoutSafe.top); 122 mState.getOrCreateSource(InsetsSource.createId(null, 2, displayCutout()), displayCutout()) 123 .setFrame(mDisplayCutoutSafe.right, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); 124 mState.getOrCreateSource(InsetsSource.createId(null, 3, displayCutout()), displayCutout()) 125 .setFrame(0, mDisplayCutoutSafe.bottom, DISPLAY_WIDTH, DISPLAY_HEIGHT); 126 } 127 assertInsetByTopBottom(int top, int bottom, Rect actual)128 private static void assertInsetByTopBottom(int top, int bottom, Rect actual) { 129 assertInsetBy(0, top, 0, bottom, actual); 130 } 131 assertInsetBy(int left, int top, int right, int bottom, Rect actual)132 private static void assertInsetBy(int left, int top, int right, int bottom, Rect actual) { 133 assertRect(left, top, DISPLAY_WIDTH - right, DISPLAY_HEIGHT - bottom, actual); 134 } 135 assertRect(int left, int top, int right, int bottom, Rect actual)136 private static void assertRect(int left, int top, int right, int bottom, Rect actual) { 137 assertEquals(new Rect(left, top, right, bottom), actual); 138 } 139 140 @Test defaultParams()141 public void defaultParams() { 142 computeFrames(); 143 144 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); 145 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame); 146 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame); 147 } 148 149 @Test unmeasured()150 public void unmeasured() { 151 mRequestedWidth = UNSPECIFIED_LENGTH; 152 mRequestedHeight = UNSPECIFIED_LENGTH; 153 computeFrames(); 154 155 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); 156 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame); 157 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame); 158 } 159 160 @Test unmeasuredWithSizeSpecifiedInLayoutParams()161 public void unmeasuredWithSizeSpecifiedInLayoutParams() { 162 final int width = DISPLAY_WIDTH / 2; 163 final int height = DISPLAY_HEIGHT / 2; 164 mRequestedWidth = UNSPECIFIED_LENGTH; 165 mRequestedHeight = UNSPECIFIED_LENGTH; 166 mAttrs.width = width; 167 mAttrs.height = height; 168 mAttrs.gravity = Gravity.LEFT | Gravity.TOP; 169 computeFrames(); 170 171 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); 172 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame); 173 assertRect(0, STATUS_BAR_HEIGHT, width, STATUS_BAR_HEIGHT + height, mFrames.frame); 174 } 175 176 @Test nonFullscreenWindowBounds()177 public void nonFullscreenWindowBounds() { 178 final int top = Math.max(DISPLAY_HEIGHT / 2, STATUS_BAR_HEIGHT); 179 mWindowBounds.set(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT); 180 mRequestedWidth = UNSPECIFIED_LENGTH; 181 mRequestedHeight = UNSPECIFIED_LENGTH; 182 computeFrames(); 183 184 assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT, 185 mFrames.displayFrame); 186 assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT, 187 mFrames.parentFrame); 188 assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT, 189 mFrames.frame); 190 } 191 192 @Test attachedFrame()193 public void attachedFrame() { 194 final int bottom = (DISPLAY_HEIGHT - STATUS_BAR_HEIGHT - NAVIGATION_BAR_HEIGHT) / 2; 195 mFrames.attachedFrame = new Rect(0, STATUS_BAR_HEIGHT, DISPLAY_WIDTH, bottom); 196 mRequestedWidth = UNSPECIFIED_LENGTH; 197 mRequestedHeight = UNSPECIFIED_LENGTH; 198 computeFrames(); 199 200 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); 201 assertEquals(mFrames.attachedFrame, mFrames.parentFrame); 202 assertEquals(mFrames.attachedFrame, mFrames.frame); 203 } 204 205 @Test fitStatusBars()206 public void fitStatusBars() { 207 mAttrs.setFitInsetsTypes(WindowInsets.Type.statusBars()); 208 computeFrames(); 209 210 assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.displayFrame); 211 assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.parentFrame); 212 assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.frame); 213 } 214 215 @Test fitNavigationBars()216 public void fitNavigationBars() { 217 mAttrs.setFitInsetsTypes(WindowInsets.Type.navigationBars()); 218 computeFrames(); 219 220 assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); 221 assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame); 222 assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.frame); 223 } 224 225 @Test fitZeroTypes()226 public void fitZeroTypes() { 227 mAttrs.setFitInsetsTypes(0); 228 computeFrames(); 229 230 assertInsetByTopBottom(0, 0, mFrames.displayFrame); 231 assertInsetByTopBottom(0, 0, mFrames.parentFrame); 232 assertInsetByTopBottom(0, 0, mFrames.frame); 233 } 234 235 @Test fitAllSides()236 public void fitAllSides() { 237 mAttrs.setFitInsetsSides(WindowInsets.Side.all()); 238 computeFrames(); 239 240 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); 241 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame); 242 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame); 243 } 244 245 @Test fitTopOnly()246 public void fitTopOnly() { 247 mAttrs.setFitInsetsSides(WindowInsets.Side.TOP); 248 computeFrames(); 249 250 assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.displayFrame); 251 assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.parentFrame); 252 assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.frame); 253 } 254 255 @Test fitZeroSides()256 public void fitZeroSides() { 257 mAttrs.setFitInsetsSides(0); 258 computeFrames(); 259 260 assertInsetByTopBottom(0, 0, mFrames.displayFrame); 261 assertInsetByTopBottom(0, 0, mFrames.parentFrame); 262 assertInsetByTopBottom(0, 0, mFrames.frame); 263 } 264 265 @Test fitInvisibleInsets()266 public void fitInvisibleInsets() { 267 mState.setSourceVisible(ID_STATUS_BAR, false); 268 mState.setSourceVisible(ID_NAVIGATION_BAR, false); 269 computeFrames(); 270 271 assertInsetByTopBottom(0, 0, mFrames.displayFrame); 272 assertInsetByTopBottom(0, 0, mFrames.parentFrame); 273 assertInsetByTopBottom(0, 0, mFrames.frame); 274 } 275 276 @Test fitInvisibleInsetsIgnoringVisibility()277 public void fitInvisibleInsetsIgnoringVisibility() { 278 mState.setSourceVisible(ID_STATUS_BAR, false); 279 mState.setSourceVisible(ID_NAVIGATION_BAR, false); 280 mAttrs.setFitInsetsIgnoringVisibility(true); 281 computeFrames(); 282 283 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); 284 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame); 285 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame); 286 } 287 288 @Test insetParentFrameByIme()289 public void insetParentFrameByIme() { 290 mState.getOrCreateSource(ID_IME, ime()) 291 .setVisible(true) 292 .setFrame(0, DISPLAY_HEIGHT - IME_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT); 293 mAttrs.privateFlags |= PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME; 294 computeFrames(); 295 296 assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); 297 assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mFrames.parentFrame); 298 assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mFrames.frame); 299 } 300 301 @Test fitDisplayCutout()302 public void fitDisplayCutout() { 303 addDisplayCutout(); 304 mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 305 mAttrs.setFitInsetsTypes(WindowInsets.Type.displayCutout()); 306 computeFrames(); 307 308 assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, 309 mFrames.displayFrame); 310 assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, 311 mFrames.parentFrame); 312 assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, 313 mFrames.frame); 314 } 315 316 @Test layoutInDisplayCutoutModeDefault()317 public void layoutInDisplayCutoutModeDefault() { 318 addDisplayCutout(); 319 mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; 320 mAttrs.setFitInsetsTypes(0); 321 computeFrames(); 322 323 assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, 324 mFrames.displayFrame); 325 assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, 326 mFrames.parentFrame); 327 assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, 328 mFrames.frame); 329 } 330 331 @Test layoutInDisplayCutoutModeDefaultWithLayoutInScreenAndLayoutInsetDecor()332 public void layoutInDisplayCutoutModeDefaultWithLayoutInScreenAndLayoutInsetDecor() { 333 addDisplayCutout(); 334 mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; 335 mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; 336 mAttrs.setFitInsetsTypes(0); 337 computeFrames(); 338 339 assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.displayFrame); 340 assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.parentFrame); 341 assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.frame); 342 } 343 344 @Test layoutExtendedToDisplayCutout()345 public void layoutExtendedToDisplayCutout() { 346 addDisplayCutout(); 347 final int height = DISPLAY_HEIGHT / 2; 348 mRequestedHeight = UNSPECIFIED_LENGTH; 349 mAttrs.height = height; 350 mAttrs.gravity = Gravity.TOP; 351 mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 352 mAttrs.setFitInsetsTypes(0); 353 mAttrs.privateFlags |= PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; 354 computeFrames(); 355 356 assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mFrames.displayFrame); 357 assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mFrames.parentFrame); 358 assertRect(0, 0, DISPLAY_WIDTH, height + DISPLAY_CUTOUT_HEIGHT, mFrames.frame); 359 } 360 361 @Test layoutInDisplayCutoutModeDefaultWithInvisibleSystemBars()362 public void layoutInDisplayCutoutModeDefaultWithInvisibleSystemBars() { 363 addDisplayCutout(); 364 mState.setSourceVisible(ID_STATUS_BAR, false); 365 mState.setSourceVisible(ID_NAVIGATION_BAR, false); 366 mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; 367 mAttrs.setFitInsetsTypes(0); 368 computeFrames(); 369 370 assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, 371 mFrames.displayFrame); 372 assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, 373 mFrames.parentFrame); 374 assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, 375 mFrames.frame); 376 } 377 378 @Test layoutInDisplayCutoutModeShortEdges()379 public void layoutInDisplayCutoutModeShortEdges() { 380 addDisplayCutout(); 381 mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; 382 mAttrs.setFitInsetsTypes(0); 383 computeFrames(); 384 385 assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.displayFrame); 386 assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.parentFrame); 387 assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.frame); 388 } 389 390 @Test layoutInDisplayCutoutModeNever()391 public void layoutInDisplayCutoutModeNever() { 392 addDisplayCutout(); 393 mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; 394 mAttrs.setFitInsetsTypes(0); 395 computeFrames(); 396 397 assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, 398 mFrames.displayFrame); 399 assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, 400 mFrames.parentFrame); 401 assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, 402 mFrames.frame); 403 } 404 405 @Test layoutInDisplayCutoutModeAlways()406 public void layoutInDisplayCutoutModeAlways() { 407 addDisplayCutout(); 408 mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 409 mAttrs.setFitInsetsTypes(0); 410 computeFrames(); 411 412 assertInsetByTopBottom(0, 0, mFrames.displayFrame); 413 assertInsetByTopBottom(0, 0, mFrames.parentFrame); 414 assertInsetByTopBottom(0, 0, mFrames.frame); 415 } 416 } 417