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 android.server.am; 18 19 import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 22 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 23 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; 24 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 25 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; 26 import static android.server.am.ComponentNameUtils.getActivityName; 27 import static android.server.am.ComponentNameUtils.getWindowName; 28 import static android.server.am.StateLogger.log; 29 import static android.server.am.StateLogger.logAlways; 30 import static android.server.am.StateLogger.logE; 31 32 import static org.hamcrest.Matchers.greaterThan; 33 import static org.hamcrest.Matchers.lessThan; 34 import static org.junit.Assert.assertEquals; 35 import static org.junit.Assert.assertNotNull; 36 import static org.junit.Assert.assertNull; 37 import static org.junit.Assert.assertThat; 38 39 import android.content.ComponentName; 40 import android.graphics.Rect; 41 import android.os.SystemClock; 42 import android.server.am.ActivityManagerState.ActivityStack; 43 import android.server.am.ActivityManagerState.ActivityTask; 44 import android.server.am.WindowManagerState.WindowStack; 45 import android.server.am.WindowManagerState.WindowState; 46 import android.server.am.WindowManagerState.WindowTask; 47 48 import java.util.ArrayList; 49 import java.util.Arrays; 50 import java.util.List; 51 import java.util.Objects; 52 import java.util.function.BiPredicate; 53 import java.util.function.BooleanSupplier; 54 import java.util.function.Predicate; 55 56 /** 57 * Combined state of the activity manager and window manager. 58 */ 59 public class ActivityAndWindowManagersState { 60 61 // Clone of android DisplayMetrics.DENSITY_DEFAULT (DENSITY_MEDIUM) 62 // (Needed in host-side tests to convert dp to px.) 63 private static final int DISPLAY_DENSITY_DEFAULT = 160; 64 65 // Default minimal size of resizable task, used if none is set explicitly. 66 // Must be kept in sync with 'default_minimal_size_resizable_task' dimen from frameworks/base. 67 private static final int DEFAULT_RESIZABLE_TASK_SIZE_DP = 220; 68 69 // Default minimal size of a resizable PiP task, used if none is set explicitly. 70 // Must be kept in sync with 'default_minimal_size_pip_resizable_task' dimen from 71 // frameworks/base. 72 private static final int DEFAULT_PIP_RESIZABLE_TASK_SIZE_DP = 108; 73 74 private ActivityManagerState mAmState = new ActivityManagerState(); 75 private WindowManagerState mWmState = new WindowManagerState(); 76 77 /** 78 * Compute AM and WM state of device, check sanity and bounds. 79 * WM state will include only visible windows, stack and task bounds will be compared. 80 * 81 * @param waitForActivitiesVisible array of activity names to wait for. 82 */ 83 public void computeState(WaitForValidActivityState... waitForActivitiesVisible) { 84 waitForValidState(true /* compareTaskAndStackBounds */, waitForActivitiesVisible); 85 } 86 87 /** 88 * Compute AM and WM state of device, check sanity and bounds. 89 * 90 * @param compareTaskAndStackBounds pass 'true' if stack and task bounds should be compared, 91 * 'false' otherwise. 92 * @param waitForActivitiesVisible array of activity states to wait for. 93 */ 94 void computeState(boolean compareTaskAndStackBounds, 95 WaitForValidActivityState... waitForActivitiesVisible) { 96 waitForValidState(compareTaskAndStackBounds, waitForActivitiesVisible); 97 } 98 99 /** Wait for the activity to appear and for valid state in AM and WM. */ 100 void waitForValidState(WaitForValidActivityState... waitForActivityVisible) { 101 waitForValidState(false /* compareTaskAndStackBounds */, waitForActivityVisible); 102 } 103 104 /** 105 * Wait for the activities to appear in proper stacks and for valid state in AM and WM. 106 * 107 * @param compareTaskAndStackBounds flag indicating if we should compare task and stack bounds 108 * for equality. 109 * @param waitForActivitiesVisible array of activity states to wait for. 110 */ 111 private void waitForValidState(boolean compareTaskAndStackBounds, 112 WaitForValidActivityState... waitForActivitiesVisible) { 113 for (int retry = 1; retry <= 5; retry++) { 114 // TODO: Get state of AM and WM at the same time to avoid mismatches caused by 115 // requesting dump in some intermediate state. 116 mAmState.computeState(); 117 mWmState.computeState(); 118 if (shouldWaitForSanityCheck(compareTaskAndStackBounds) 119 || shouldWaitForValidStacks(compareTaskAndStackBounds) 120 || shouldWaitForActivities(waitForActivitiesVisible) 121 || shouldWaitForWindows()) { 122 logAlways("***Waiting for valid stacks and activities states... retry=" + retry); 123 SystemClock.sleep(1000); 124 } else { 125 return; 126 } 127 } 128 logE("***Waiting for states failed: " + Arrays.toString(waitForActivitiesVisible)); 129 } 130 131 boolean waitForHomeActivityVisible() { 132 ComponentName homeActivity = mAmState.getHomeActivityName(); 133 // Sometimes this function is called before we know what Home Activity is 134 if (homeActivity == null) { 135 log("Computing state to determine Home Activity"); 136 computeState(true); 137 homeActivity = mAmState.getHomeActivityName(); 138 } 139 assertNotNull("homeActivity should not be null", homeActivity); 140 waitForValidState(new WaitForValidActivityState(homeActivity)); 141 return mAmState.isHomeActivityVisible(); 142 } 143 144 public void waitForKeyguardShowingAndNotOccluded() { 145 waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing 146 && !state.getKeyguardControllerState().keyguardOccluded, 147 "***Waiting for Keyguard showing..."); 148 } 149 150 @Deprecated 151 void waitForFocusedStack(int stackId) { 152 waitForWithAmState(state -> state.getFocusedStackId() == stackId, 153 "***Waiting for focused stack..."); 154 } 155 156 void waitForWithAmState(Predicate<ActivityManagerState> waitCondition, String message) { 157 waitFor((amState, wmState) -> waitCondition.test(amState), message); 158 } 159 160 void waitFor( 161 BiPredicate<ActivityManagerState, WindowManagerState> waitCondition, String message) { 162 waitFor(message, () -> { 163 mAmState.computeState(); 164 mWmState.computeState(); 165 return waitCondition.test(mAmState, mWmState); 166 }); 167 } 168 169 void waitFor(String message, BooleanSupplier waitCondition) { 170 for (int retry = 1; retry <= 5; retry++) { 171 if (waitCondition.getAsBoolean()) { 172 return; 173 } 174 logAlways(message + " retry=" + retry); 175 SystemClock.sleep(1000); 176 } 177 logE(message + " failed"); 178 } 179 180 /** 181 * @return true if should wait for valid stacks state. 182 */ 183 private boolean shouldWaitForValidStacks(boolean compareTaskAndStackBounds) { 184 if (!taskListsInAmAndWmAreEqual()) { 185 // We want to wait for equal task lists in AM and WM in case we caught them in the 186 // middle of some state change operations. 187 logAlways("***taskListsInAmAndWmAreEqual=false"); 188 return true; 189 } 190 if (!stackBoundsInAMAndWMAreEqual()) { 191 // We want to wait a little for the stacks in AM and WM to have equal bounds as there 192 // might be a transition animation ongoing when we got the states from WM AM separately. 193 logAlways("***stackBoundsInAMAndWMAreEqual=false"); 194 return true; 195 } 196 try { 197 // Temporary fix to avoid catching intermediate state with different task bounds in AM 198 // and WM. 199 assertValidBounds(compareTaskAndStackBounds); 200 } catch (AssertionError e) { 201 logAlways("***taskBoundsInAMAndWMAreEqual=false : " + e.getMessage()); 202 return true; 203 } 204 final int stackCount = mAmState.getStackCount(); 205 if (stackCount == 0) { 206 logAlways("***stackCount=" + stackCount); 207 return true; 208 } 209 final int resumedActivitiesCount = mAmState.getResumedActivitiesCount(); 210 if (!mAmState.getKeyguardControllerState().keyguardShowing && resumedActivitiesCount != 1) { 211 logAlways("***resumedActivitiesCount=" + resumedActivitiesCount); 212 return true; 213 } 214 if (mAmState.getFocusedActivity() == null) { 215 logAlways("***focusedActivity=null"); 216 return true; 217 } 218 return false; 219 } 220 221 /** 222 * @return true if should wait for some activities to become visible. 223 */ 224 private boolean shouldWaitForActivities(WaitForValidActivityState... waitForActivitiesVisible) { 225 if (waitForActivitiesVisible == null || waitForActivitiesVisible.length == 0) { 226 return false; 227 } 228 // If the caller is interested in us waiting for some particular activity windows to be 229 // visible before compute the state. Check for the visibility of those activity windows 230 // and for placing them in correct stacks (if requested). 231 boolean allActivityWindowsVisible = true; 232 boolean tasksInCorrectStacks = true; 233 List<WindowState> matchingWindowStates = new ArrayList<>(); 234 for (final WaitForValidActivityState state : waitForActivitiesVisible) { 235 final ComponentName activityName = state.activityName; 236 final String windowName = state.windowName; 237 final int stackId = state.stackId; 238 final int windowingMode = state.windowingMode; 239 final int activityType = state.activityType; 240 241 mWmState.getMatchingVisibleWindowState(windowName, matchingWindowStates); 242 boolean activityWindowVisible = !matchingWindowStates.isEmpty(); 243 if (!activityWindowVisible) { 244 logAlways("Activity window not visible: " + windowName); 245 allActivityWindowsVisible = false; 246 } else if (activityName != null 247 && !mAmState.isActivityVisible(activityName)) { 248 logAlways("Activity not visible: " + getActivityName(activityName)); 249 allActivityWindowsVisible = false; 250 } else { 251 // Check if window is already the correct state requested by test. 252 boolean windowInCorrectState = false; 253 for (WindowState ws : matchingWindowStates) { 254 if (stackId != INVALID_STACK_ID && ws.getStackId() != stackId) { 255 continue; 256 } 257 if (windowingMode != WINDOWING_MODE_UNDEFINED 258 && ws.getWindowingMode() != windowingMode) { 259 continue; 260 } 261 if (activityType != ACTIVITY_TYPE_UNDEFINED 262 && ws.getActivityType() != activityType) { 263 continue; 264 } 265 windowInCorrectState = true; 266 break; 267 } 268 269 if (!windowInCorrectState) { 270 logAlways("Window in incorrect stack: " + state); 271 tasksInCorrectStacks = false; 272 } 273 } 274 } 275 return !allActivityWindowsVisible || !tasksInCorrectStacks; 276 } 277 278 /** 279 * @return true if should wait valid windows state. 280 */ 281 private boolean shouldWaitForWindows() { 282 if (mWmState.getFrontWindow() == null) { 283 logAlways("***frontWindow=null"); 284 return true; 285 } 286 if (mWmState.getFocusedWindow() == null) { 287 logAlways("***focusedWindow=null"); 288 return true; 289 } 290 if (mWmState.getFocusedApp() == null) { 291 logAlways("***focusedApp=null"); 292 return true; 293 } 294 295 return false; 296 } 297 298 private boolean shouldWaitForSanityCheck(boolean compareTaskAndStackBounds) { 299 try { 300 assertSanity(); 301 assertValidBounds(compareTaskAndStackBounds); 302 } catch (Throwable t) { 303 logAlways("Waiting for sanity check: " + t.toString()); 304 return true; 305 } 306 return false; 307 } 308 309 public WindowManagerState getWmState() { 310 return mWmState; 311 } 312 313 void assertSanity() { 314 assertThat("Must have stacks", mAmState.getStackCount(), greaterThan(0)); 315 if (!mAmState.getKeyguardControllerState().keyguardShowing) { 316 assertEquals("There should be one and only one resumed activity in the system.", 317 1, mAmState.getResumedActivitiesCount()); 318 } 319 assertNotNull("Must have focus activity.", mAmState.getFocusedActivity()); 320 321 for (ActivityStack aStack : mAmState.getStacks()) { 322 final int stackId = aStack.mStackId; 323 for (ActivityTask aTask : aStack.getTasks()) { 324 assertEquals("Stack can only contain its own tasks", stackId, aTask.mStackId); 325 } 326 } 327 328 assertNotNull("Must have front window.", mWmState.getFrontWindow()); 329 assertNotNull("Must have focused window.", mWmState.getFocusedWindow()); 330 assertNotNull("Must have app.", mWmState.getFocusedApp()); 331 } 332 333 @Deprecated 334 void assertFocusedStack(String msg, int stackId) { 335 assertEquals(msg, stackId, mAmState.getFocusedStackId()); 336 } 337 338 public void assertVisibility(final ComponentName activityName, final boolean visible) { 339 final String windowName = getWindowName(activityName); 340 final boolean activityVisible = mAmState.isActivityVisible(activityName); 341 final boolean windowVisible = mWmState.isWindowVisible(windowName); 342 343 assertEquals("Activity=" + getActivityName(activityName) + " must" + (visible ? "" : " NOT") 344 + " be visible.", visible, activityVisible); 345 assertEquals("Window=" + windowName + " must" + (visible ? "" : " NOT") + " be visible.", 346 visible, windowVisible); 347 } 348 349 boolean taskListsInAmAndWmAreEqual() { 350 for (ActivityStack aStack : mAmState.getStacks()) { 351 final int stackId = aStack.mStackId; 352 final WindowStack wStack = mWmState.getStack(stackId); 353 if (wStack == null) { 354 log("Waiting for stack setup in WM, stackId=" + stackId); 355 return false; 356 } 357 358 for (ActivityTask aTask : aStack.getTasks()) { 359 if (wStack.getTask(aTask.mTaskId) == null) { 360 log("Task is in AM but not in WM, waiting for it to settle, taskId=" 361 + aTask.mTaskId); 362 return false; 363 } 364 } 365 366 for (WindowTask wTask : wStack.mTasks) { 367 if (aStack.getTask(wTask.mTaskId) == null) { 368 log("Task is in WM but not in AM, waiting for it to settle, taskId=" 369 + wTask.mTaskId); 370 return false; 371 } 372 } 373 } 374 return true; 375 } 376 377 boolean stackBoundsInAMAndWMAreEqual() { 378 for (ActivityStack aStack : mAmState.getStacks()) { 379 final int stackId = aStack.mStackId; 380 final WindowStack wStack = mWmState.getStack(stackId); 381 if (aStack.isFullscreen() != wStack.isFullscreen()) { 382 log("Waiting for correct fullscreen state, stackId=" + stackId); 383 return false; 384 } 385 386 final Rect aStackBounds = aStack.getBounds(); 387 final Rect wStackBounds = wStack.getBounds(); 388 389 if (aStack.isFullscreen()) { 390 if (aStackBounds != null) { 391 log("Waiting for correct stack state in AM, stackId=" + stackId); 392 return false; 393 } 394 } else if (!Objects.equals(aStackBounds, wStackBounds)) { 395 // If stack is not fullscreen - comparing bounds. Not doing it always because 396 // for fullscreen stack bounds in WM can be either null or equal to display size. 397 log("Waiting for stack bound equality in AM and WM, stackId=" + stackId); 398 return false; 399 } 400 } 401 402 return true; 403 } 404 405 void assertValidBounds(boolean compareTaskAndStackBounds) { 406 // Cycle through the stacks and tasks to figure out if the home stack is resizable 407 final ActivityTask homeTask = mAmState.getHomeTask(); 408 final boolean homeStackIsResizable = homeTask != null 409 && homeTask.getResizeMode() == RESIZE_MODE_RESIZEABLE; 410 411 for (ActivityStack aStack : mAmState.getStacks()) { 412 final int stackId = aStack.mStackId; 413 final WindowStack wStack = mWmState.getStack(stackId); 414 assertNotNull("stackId=" + stackId + " in AM but not in WM?", wStack); 415 416 assertEquals("Stack fullscreen state in AM and WM must be equal stackId=" + stackId, 417 aStack.isFullscreen(), wStack.isFullscreen()); 418 419 final Rect aStackBounds = aStack.getBounds(); 420 final Rect wStackBounds = wStack.getBounds(); 421 422 if (aStack.isFullscreen()) { 423 assertNull("Stack bounds in AM must be null stackId=" + stackId, aStackBounds); 424 } else { 425 assertEquals("Stack bounds in AM and WM must be equal stackId=" + stackId, 426 aStackBounds, wStackBounds); 427 } 428 429 for (ActivityTask aTask : aStack.getTasks()) { 430 final int taskId = aTask.mTaskId; 431 final WindowTask wTask = wStack.getTask(taskId); 432 assertNotNull( 433 "taskId=" + taskId + " in AM but not in WM? stackId=" + stackId, wTask); 434 435 final boolean aTaskIsFullscreen = aTask.isFullscreen(); 436 final boolean wTaskIsFullscreen = wTask.isFullscreen(); 437 assertEquals("Task fullscreen state in AM and WM must be equal taskId=" + taskId 438 + ", stackId=" + stackId, aTaskIsFullscreen, wTaskIsFullscreen); 439 440 final Rect aTaskBounds = aTask.getBounds(); 441 final Rect wTaskBounds = wTask.getBounds(); 442 443 if (aTaskIsFullscreen) { 444 assertNull("Task bounds in AM must be null for fullscreen taskId=" + taskId, 445 aTaskBounds); 446 } else if (!homeStackIsResizable && mWmState.isDockedStackMinimized() 447 && !isScreenPortrait(aStack.mDisplayId)) { 448 // When minimized using non-resizable launcher in landscape mode, it will move 449 // the task offscreen in the negative x direction unlike portrait that crops. 450 // The x value in the task bounds will not match the stack bounds since the 451 // only the task was moved. 452 assertEquals("Task bounds in AM and WM must match width taskId=" + taskId 453 + ", stackId" + stackId, aTaskBounds.width(), 454 wTaskBounds.width()); 455 assertEquals("Task bounds in AM and WM must match height taskId=" + taskId 456 + ", stackId" + stackId, aTaskBounds.height(), 457 wTaskBounds.height()); 458 assertEquals("Task bounds must match stack bounds y taskId=" + taskId 459 + ", stackId" + stackId, aTaskBounds.top, 460 wTaskBounds.top); 461 assertEquals("Task and stack bounds must match width taskId=" + taskId 462 + ", stackId" + stackId, aStackBounds.width(), 463 wTaskBounds.width()); 464 assertEquals("Task and stack bounds must match height taskId=" + taskId 465 + ", stackId" + stackId, aStackBounds.height(), 466 wTaskBounds.height()); 467 assertEquals("Task and stack bounds must match y taskId=" + taskId 468 + ", stackId" + stackId, aStackBounds.top, 469 wTaskBounds.top); 470 } else { 471 assertEquals("Task bounds in AM and WM must be equal taskId=" + taskId 472 + ", stackId=" + stackId, aTaskBounds, wTaskBounds); 473 474 if (compareTaskAndStackBounds 475 && aStack.getWindowingMode() != WINDOWING_MODE_FREEFORM) { 476 int aTaskMinWidth = aTask.getMinWidth(); 477 int aTaskMinHeight = aTask.getMinHeight(); 478 479 if (aTaskMinWidth == -1 || aTaskMinHeight == -1) { 480 // Minimal dimension(s) not set for task - it should be using defaults. 481 int defaultMinimalSize = 482 aStack.getWindowingMode() == WINDOWING_MODE_PINNED 483 ? defaultMinimalPinnedTaskSize(aStack.mDisplayId) 484 : defaultMinimalTaskSize(aStack.mDisplayId); 485 486 if (aTaskMinWidth == -1) { 487 aTaskMinWidth = defaultMinimalSize; 488 } 489 if (aTaskMinHeight == -1) { 490 aTaskMinHeight = defaultMinimalSize; 491 } 492 } 493 494 if (aStackBounds.width() >= aTaskMinWidth 495 && aStackBounds.height() >= aTaskMinHeight 496 || aStack.getWindowingMode() == WINDOWING_MODE_PINNED) { 497 // Bounds are not smaller then minimal possible, so stack and task 498 // bounds must be equal. 499 assertEquals("Task bounds must be equal to stack bounds taskId=" 500 + taskId + ", stackId=" + stackId, aStackBounds, wTaskBounds); 501 } else if (aStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY 502 && homeStackIsResizable && mWmState.isDockedStackMinimized()) { 503 // Portrait if the display height is larger than the width 504 if (isScreenPortrait(aStack.mDisplayId)) { 505 assertEquals("Task width must be equal to stack width taskId=" 506 + taskId + ", stackId=" + stackId, 507 aStackBounds.width(), wTaskBounds.width()); 508 assertThat("Task height must be greater than stack height " 509 + "taskId=" + taskId + ", stackId=" + stackId, 510 aStackBounds.height(), lessThan(wTaskBounds.height())); 511 assertEquals("Task and stack x position must be equal taskId=" 512 + taskId + ", stackId=" + stackId, 513 wTaskBounds.left, wStackBounds.left); 514 } else { 515 assertThat("Task width must be greater than stack width taskId=" 516 + taskId + ", stackId=" + stackId, 517 aStackBounds.width(), lessThan(wTaskBounds.width())); 518 assertEquals("Task height must be equal to stack height taskId=" 519 + taskId + ", stackId=" + stackId, 520 aStackBounds.height(), wTaskBounds.height()); 521 assertEquals("Task and stack y position must be equal taskId=" 522 + taskId + ", stackId=" + stackId, wTaskBounds.top, 523 wStackBounds.top); 524 } 525 } else { 526 // Minimal dimensions affect task size, so bounds of task and stack must 527 // be different - will compare dimensions instead. 528 int targetWidth = (int) Math.max(aTaskMinWidth, 529 aStackBounds.width()); 530 assertEquals("Task width must be set according to minimal width" 531 + " taskId=" + taskId + ", stackId=" + stackId, 532 targetWidth, (int) wTaskBounds.width()); 533 int targetHeight = (int) Math.max(aTaskMinHeight, 534 aStackBounds.height()); 535 assertEquals("Task height must be set according to minimal height" 536 + " taskId=" + taskId + ", stackId=" + stackId, 537 targetHeight, (int) wTaskBounds.height()); 538 } 539 } 540 } 541 } 542 } 543 } 544 545 boolean isScreenPortrait(int displayId) { 546 final Rect displayRect = mWmState.getDisplay(displayId).getDisplayRect(); 547 return displayRect.height() > displayRect.width(); 548 } 549 550 static int dpToPx(float dp, int densityDpi) { 551 return (int) (dp * densityDpi / DISPLAY_DENSITY_DEFAULT + 0.5f); 552 } 553 554 private int defaultMinimalTaskSize(int displayId) { 555 return dpToPx(DEFAULT_RESIZABLE_TASK_SIZE_DP, mWmState.getDisplay(displayId).getDpi()); 556 } 557 558 private int defaultMinimalPinnedTaskSize(int displayId) { 559 return dpToPx(DEFAULT_PIP_RESIZABLE_TASK_SIZE_DP, mWmState.getDisplay(displayId).getDpi()); 560 } 561 } 562