1 /* 2 * Copyright (C) 2016 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 com.android.server.wm; 18 19 import org.junit.Before; 20 import org.junit.Test; 21 import org.junit.runner.RunWith; 22 23 import android.app.ActivityManager.TaskDescription; 24 import android.content.Context; 25 import android.graphics.Rect; 26 import android.platform.test.annotations.Presubmit; 27 import android.support.test.InstrumentationRegistry; 28 import android.support.test.filters.SmallTest; 29 import android.support.test.runner.AndroidJUnit4; 30 import android.view.DisplayInfo; 31 import android.view.Gravity; 32 import android.view.IWindow; 33 import android.view.WindowManager; 34 35 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; 36 import static android.view.WindowManager.LayoutParams.FILL_PARENT; 37 import static org.junit.Assert.assertEquals; 38 import static org.junit.Assert.assertTrue; 39 40 /** 41 * Tests for the {@link WindowState#computeFrameLw} method and other window frame machinery. 42 * 43 * Build/Install/Run: bit FrameworksServicesTests:com.android.server.wm.WindowFrameTests 44 */ 45 @SmallTest 46 @Presubmit 47 @RunWith(AndroidJUnit4.class) 48 public class WindowFrameTests extends WindowTestsBase { 49 50 private WindowToken mWindowToken; 51 private final IWindow mIWindow = new TestIWindow(); 52 53 class WindowStateWithTask extends WindowState { 54 final Task mTask; 55 boolean mDockedResizingForTest = false; 56 WindowStateWithTask(WindowManager.LayoutParams attrs, Task t) { 57 super(sWm, null, mIWindow, mWindowToken, null, 0, 0, attrs, 0, 0, 58 false /* ownerCanAddInternalSystemWindow */); 59 mTask = t; 60 } 61 62 @Override 63 Task getTask() { 64 return mTask; 65 } 66 67 @Override 68 boolean isDockedResizing() { 69 return mDockedResizingForTest; 70 } 71 }; 72 73 class TaskWithBounds extends Task { 74 final Rect mBounds; 75 final Rect mInsetBounds = new Rect(); 76 boolean mFullscreenForTest = true; 77 TaskWithBounds(Rect bounds) { 78 super(0, mStubStack, 0, sWm, null, null, 0, false, false, new TaskDescription(), null); 79 mBounds = bounds; 80 } 81 @Override 82 void getBounds(Rect outBounds) { 83 outBounds.set(mBounds); 84 } 85 @Override 86 void getTempInsetBounds(Rect outBounds) { 87 outBounds.set(mInsetBounds); 88 } 89 @Override 90 boolean isFullscreen() { 91 return mFullscreenForTest; 92 } 93 } 94 95 TaskStack mStubStack; 96 97 @Before 98 public void setUp() throws Exception { 99 super.setUp(); 100 101 // Just any non zero value. 102 sWm.mSystemDecorLayer = 10000; 103 104 mWindowToken = new WindowTestUtils.TestAppWindowToken(sWm.getDefaultDisplayContentLocked()); 105 mStubStack = new TaskStack(sWm, 0); 106 } 107 108 public void assertRect(Rect rect, int left, int top, int right, int bottom) { 109 assertEquals(left, rect.left); 110 assertEquals(top, rect.top); 111 assertEquals(right, rect.right); 112 assertEquals(bottom, rect.bottom); 113 } 114 115 @Test 116 public void testLayoutInFullscreenTaskInsets() throws Exception { 117 Task task = new TaskWithBounds(null); // fullscreen task doesn't use bounds for computeFrame 118 WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); 119 w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; 120 121 final int bottomContentInset = 100; 122 final int topContentInset = 50; 123 final int bottomVisibleInset = 30; 124 final int topVisibleInset = 70; 125 final int leftStableInset = 20; 126 final int rightStableInset = 90; 127 128 // With no insets or system decor all the frames incoming from PhoneWindowManager 129 // are identical. 130 final Rect pf = new Rect(0, 0, 1000, 1000); 131 final Rect df = pf; 132 final Rect of = df; 133 final Rect cf = new Rect(pf); 134 // Produce some insets 135 cf.top += 50; 136 cf.bottom -= 100; 137 final Rect vf = new Rect(pf); 138 vf.top += topVisibleInset; 139 vf.bottom -= bottomVisibleInset; 140 final Rect sf = new Rect(pf); 141 sf.left += leftStableInset; 142 sf.right -= rightStableInset; 143 144 final Rect dcf = pf; 145 // When mFrame extends past cf, the content insets are 146 // the difference between mFrame and ContentFrame. Visible 147 // and stable frames work the same way. 148 w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null); 149 assertRect(w.mFrame,0, 0, 1000, 1000); 150 assertRect(w.mContentInsets, 0, topContentInset, 0, bottomContentInset); 151 assertRect(w.mVisibleInsets, 0, topVisibleInset, 0, bottomVisibleInset); 152 assertRect(w.mStableInsets, leftStableInset, 0, rightStableInset, 0); 153 // The frames remain as passed in shrunk to the window frame 154 assertTrue(cf.equals(w.getContentFrameLw())); 155 assertTrue(vf.equals(w.getVisibleFrameLw())); 156 assertTrue(sf.equals(w.getStableFrameLw())); 157 // On the other hand mFrame doesn't extend past cf we won't get any insets 158 w.mAttrs.x = 100; 159 w.mAttrs.y = 100; 160 w.mAttrs.width = 100; w.mAttrs.height = 100; //have to clear MATCH_PARENT 161 w.mRequestedWidth = 100; 162 w.mRequestedHeight = 100; 163 w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null); 164 assertRect(w.mFrame, 100, 100, 200, 200); 165 assertRect(w.mContentInsets, 0, 0, 0, 0); 166 // In this case the frames are shrunk to the window frame. 167 assertTrue(w.mFrame.equals(w.getContentFrameLw())); 168 assertTrue(w.mFrame.equals(w.getVisibleFrameLw())); 169 assertTrue(w.mFrame.equals(w.getStableFrameLw())); 170 } 171 172 @Test 173 public void testLayoutInFullscreenTaskNoInsets() throws Exception { 174 Task task = new TaskWithBounds(null); // fullscreen task doesn't use bounds for computeFrame 175 WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); 176 w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; 177 178 // With no insets or system decor all the frames incoming from PhoneWindowManager 179 // are identical. 180 final Rect pf = new Rect(0, 0, 1000, 1000); 181 182 // Here the window has FILL_PARENT, FILL_PARENT 183 // so we expect it to fill the entire available frame. 184 w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf); 185 assertRect(w.mFrame, 0, 0, 1000, 1000); 186 187 // It can select various widths and heights within the bounds. 188 // Strangely the window attribute width is ignored for normal windows 189 // and we use mRequestedWidth/mRequestedHeight 190 w.mAttrs.width = 300; 191 w.mAttrs.height = 300; 192 w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf); 193 // Explicit width and height without requested width/height 194 // gets us nothing. 195 assertRect(w.mFrame, 0, 0, 0, 0); 196 197 w.mRequestedWidth = 300; 198 w.mRequestedHeight = 300; 199 w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf); 200 // With requestedWidth/Height we can freely choose our size within the 201 // parent bounds. 202 assertRect(w.mFrame, 0, 0, 300, 300); 203 204 // With FLAG_SCALED though, requestedWidth/height is used to control 205 // the unscaled surface size, and mAttrs.width/height becomes the 206 // layout controller. 207 w.mAttrs.flags = WindowManager.LayoutParams.FLAG_SCALED; 208 w.mRequestedHeight = -1; 209 w.mRequestedWidth = -1; 210 w.mAttrs.width = 100; 211 w.mAttrs.height = 100; 212 w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf); 213 assertRect(w.mFrame, 0, 0, 100, 100); 214 w.mAttrs.flags = 0; 215 216 // But sizes too large will be clipped to the containing frame 217 w.mRequestedWidth = 1200; 218 w.mRequestedHeight = 1200; 219 w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf); 220 assertRect(w.mFrame, 0, 0, 1000, 1000); 221 222 // Before they are clipped though windows will be shifted 223 w.mAttrs.x = 300; 224 w.mAttrs.y = 300; 225 w.mRequestedWidth = 1000; 226 w.mRequestedHeight = 1000; 227 w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf); 228 assertRect(w.mFrame, 0, 0, 1000, 1000); 229 230 // If there is room to move around in the parent frame the window will be shifted according 231 // to gravity. 232 w.mAttrs.x = 0; 233 w.mAttrs.y = 0; 234 w.mRequestedWidth = 300; 235 w.mRequestedHeight = 300; 236 w.mAttrs.gravity = Gravity.RIGHT | Gravity.TOP; 237 w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf); 238 assertRect(w.mFrame, 700, 0, 1000, 300); 239 w.mAttrs.gravity = Gravity.RIGHT | Gravity.BOTTOM; 240 w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf); 241 assertRect(w.mFrame, 700, 700, 1000, 1000); 242 // Window specified x and y are interpreted as offsets in the opposite 243 // direction of gravity 244 w.mAttrs.x = 100; 245 w.mAttrs.y = 100; 246 w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf); 247 assertRect(w.mFrame, 600, 600, 900, 900); 248 } 249 250 @Test 251 public void testLayoutNonfullscreenTask() { 252 final DisplayInfo displayInfo = sWm.getDefaultDisplayContentLocked().getDisplayInfo(); 253 final int logicalWidth = displayInfo.logicalWidth; 254 final int logicalHeight = displayInfo.logicalHeight; 255 256 final int taskLeft = logicalWidth / 4; 257 final int taskTop = logicalHeight / 4; 258 final int taskRight = logicalWidth / 4 * 3; 259 final int taskBottom = logicalHeight / 4 * 3; 260 final Rect taskBounds = new Rect(taskLeft, taskTop, taskRight, taskBottom); 261 TaskWithBounds task = new TaskWithBounds(taskBounds); 262 task.mFullscreenForTest = false; 263 WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); 264 w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; 265 266 final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight); 267 w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, null); 268 // For non fullscreen tasks the containing frame is based off the 269 // task bounds not the parent frame. 270 assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); 271 assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight, taskBottom); 272 assertRect(w.mContentInsets, 0, 0, 0, 0); 273 274 pf.set(0, 0, logicalWidth, logicalHeight); 275 // We still produce insets against the containing frame the same way. 276 final int cfRight = logicalWidth / 2; 277 final int cfBottom = logicalHeight / 2; 278 final Rect cf = new Rect(0, 0, cfRight, cfBottom); 279 w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null); 280 assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); 281 int contentInsetRight = taskRight - cfRight; 282 int contentInsetBottom = taskBottom - cfBottom; 283 assertRect(w.mContentInsets, 0, 0, contentInsetRight, contentInsetBottom); 284 assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight - contentInsetRight, 285 taskBottom - contentInsetBottom); 286 287 pf.set(0, 0, logicalWidth, logicalHeight); 288 // However if we set temp inset bounds, the insets will be computed 289 // as if our window was laid out there, but it will be laid out according to 290 // the task bounds. 291 final int insetLeft = logicalWidth / 5; 292 final int insetTop = logicalHeight / 5; 293 final int insetRight = insetLeft + (taskRight - taskLeft); 294 final int insetBottom = insetTop + (taskBottom - taskTop); 295 task.mInsetBounds.set(insetLeft, insetTop, insetRight, insetBottom); 296 w.computeFrameLw(pf, pf, pf, cf, cf, pf, cf, null); 297 assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); 298 contentInsetRight = insetRight - cfRight; 299 contentInsetBottom = insetBottom - cfBottom; 300 assertRect(w.mContentInsets, 0, 0, contentInsetRight, contentInsetBottom); 301 assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight - contentInsetRight, 302 taskBottom - contentInsetBottom); 303 } 304 305 @Test 306 public void testCalculatePolicyCrop() { 307 final WindowStateWithTask w = createWindow( 308 new TaskWithBounds(null), FILL_PARENT, FILL_PARENT); 309 w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; 310 311 final DisplayInfo displayInfo = w.getDisplayContent().getDisplayInfo(); 312 final int logicalWidth = displayInfo.logicalWidth; 313 final int logicalHeight = displayInfo.logicalHeight; 314 final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight); 315 final Rect df = pf; 316 final Rect of = df; 317 final Rect cf = new Rect(pf); 318 // Produce some insets 319 cf.top += displayInfo.logicalWidth / 10; 320 cf.bottom -= displayInfo.logicalWidth / 5; 321 final Rect vf = cf; 322 final Rect sf = vf; 323 // We use a decor content frame with insets to produce cropping. 324 Rect dcf = cf; 325 326 final Rect policyCrop = new Rect(); 327 328 w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null); 329 w.calculatePolicyCrop(policyCrop); 330 // If we were above system decor we wouldnt' get any cropping though 331 w.mLayer = sWm.mSystemDecorLayer + 1; 332 w.calculatePolicyCrop(policyCrop); 333 assertRect(policyCrop, 0, 0, logicalWidth, logicalHeight); 334 w.mLayer = 1; 335 dcf.setEmpty(); 336 // Likewise with no decor frame we would get no crop 337 w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null); 338 w.calculatePolicyCrop(policyCrop); 339 assertRect(policyCrop, 0, 0, logicalWidth, logicalHeight); 340 341 // Now we set up a window which doesn't fill the entire decor frame. 342 // Normally it would be cropped to it's frame but in the case of docked resizing 343 // we need to account for the fact the windows surface will be made 344 // fullscreen and thus also make the crop fullscreen. 345 w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; 346 w.mAttrs.width = logicalWidth / 2; 347 w.mAttrs.height = logicalHeight / 2; 348 w.mRequestedWidth = logicalWidth / 2; 349 w.mRequestedHeight = logicalHeight / 2; 350 w.computeFrameLw(pf, pf, pf, pf, pf, pf, pf, pf); 351 352 w.calculatePolicyCrop(policyCrop); 353 // Normally the crop is shrunk from the decor frame 354 // to the computed window frame. 355 assertRect(policyCrop, 0, 0, logicalWidth / 2, logicalHeight / 2); 356 357 w.mDockedResizingForTest = true; 358 w.calculatePolicyCrop(policyCrop); 359 // But if we are docked resizing it won't be, however we will still be 360 // shrunk to the decor frame and the display. 361 assertRect(policyCrop, 0, 0, 362 Math.min(pf.width(), displayInfo.logicalWidth), 363 Math.min(pf.height(), displayInfo.logicalHeight)); 364 } 365 366 @Test 367 public void testLayoutLetterboxedWindow() { 368 // First verify task behavior in multi-window mode. 369 final DisplayInfo displayInfo = sWm.getDefaultDisplayContentLocked().getDisplayInfo(); 370 final int logicalWidth = displayInfo.logicalWidth; 371 final int logicalHeight = displayInfo.logicalHeight; 372 373 final int taskLeft = logicalWidth / 5; 374 final int taskTop = logicalHeight / 5; 375 final int taskRight = logicalWidth / 4 * 3; 376 final int taskBottom = logicalHeight / 4 * 3; 377 final Rect taskBounds = new Rect(taskLeft, taskTop, taskRight, taskBottom); 378 TaskWithBounds task = new TaskWithBounds(taskBounds); 379 task.mInsetBounds.set(taskLeft, taskTop, taskRight, taskBottom); 380 task.mFullscreenForTest = false; 381 WindowState w = createWindow(task, FILL_PARENT, FILL_PARENT); 382 w.mAttrs.gravity = Gravity.LEFT | Gravity.TOP; 383 384 final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight); 385 w.computeFrameLw(pf /* parentFrame */, pf /* displayFrame */, pf /* overscanFrame */, 386 pf /* contentFrame */, pf /* visibleFrame */, pf /* decorFrame */, 387 pf /* stableFrame */, null /* outsetFrame */); 388 // For non fullscreen tasks the containing frame is based off the 389 // task bounds not the parent frame. 390 assertRect(w.mFrame, taskLeft, taskTop, taskRight, taskBottom); 391 assertRect(w.getContentFrameLw(), taskLeft, taskTop, taskRight, taskBottom); 392 assertRect(w.mContentInsets, 0, 0, 0, 0); 393 394 // Now simulate switch to fullscreen for letterboxed app. 395 final int xInset = logicalWidth / 10; 396 final int yInset = logicalWidth / 10; 397 final Rect cf = new Rect(xInset, yInset, logicalWidth - xInset, logicalHeight - yInset); 398 w.mAppToken.onOverrideConfigurationChanged(w.mAppToken.getOverrideConfiguration(), cf); 399 pf.set(0, 0, logicalWidth, logicalHeight); 400 task.mFullscreenForTest = true; 401 402 w.computeFrameLw(pf /* parentFrame */, pf /* displayFrame */, pf /* overscanFrame */, 403 cf /* contentFrame */, cf /* visibleFrame */, pf /* decorFrame */, 404 cf /* stableFrame */, null /* outsetFrame */); 405 assertEquals(cf, w.mFrame); 406 assertEquals(cf, w.getContentFrameLw()); 407 assertRect(w.mContentInsets, 0, 0, 0, 0); 408 } 409 410 private WindowStateWithTask createWindow(Task task, int width, int height) { 411 final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION); 412 attrs.width = width; 413 attrs.height = height; 414 415 return new WindowStateWithTask(attrs, task); 416 } 417 } 418