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 com.android.server.wm; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 22 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; 23 import static android.view.WindowManager.TRANSIT_CLOSE; 24 import static android.view.WindowManager.TRANSIT_OPEN; 25 import static android.view.WindowManager.TRANSIT_TO_BACK; 26 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; 27 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; 28 import static android.window.TransitionInfo.isIndependent; 29 30 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 31 32 import static org.junit.Assert.assertEquals; 33 import static org.junit.Assert.assertFalse; 34 import static org.junit.Assert.assertNotNull; 35 import static org.junit.Assert.assertTrue; 36 import static org.mockito.ArgumentMatchers.eq; 37 import static org.mockito.Mockito.mock; 38 import static org.mockito.Mockito.spy; 39 import static org.mockito.Mockito.times; 40 import static org.mockito.Mockito.verify; 41 42 import android.os.IBinder; 43 import android.platform.test.annotations.Presubmit; 44 import android.util.ArrayMap; 45 import android.util.ArraySet; 46 import android.view.SurfaceControl; 47 import android.window.ITaskOrganizer; 48 import android.window.ITransitionPlayer; 49 import android.window.TransitionInfo; 50 51 import androidx.test.filters.SmallTest; 52 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 56 import java.util.concurrent.CountDownLatch; 57 import java.util.concurrent.TimeUnit; 58 59 /** 60 * Build/Install/Run: 61 * atest WmTests:TransitionTests 62 */ 63 @SmallTest 64 @Presubmit 65 @RunWith(WindowTestRunner.class) 66 public class TransitionTests extends WindowTestsBase { 67 createTestTransition(int transitType)68 private Transition createTestTransition(int transitType) { 69 TransitionController controller = mock(TransitionController.class); 70 final BLASTSyncEngine sync = createTestBLASTSyncEngine(); 71 return new Transition(transitType, 0 /* flags */, 0 /* timeoutMs */, controller, sync); 72 } 73 74 @Test testCreateInfo_NewTask()75 public void testCreateInfo_NewTask() { 76 final Transition transition = createTestTransition(TRANSIT_OPEN); 77 ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; 78 ArraySet<WindowContainer> participants = transition.mParticipants; 79 80 final Task newTask = createTask(mDisplayContent); 81 final Task oldTask = createTask(mDisplayContent); 82 final ActivityRecord closing = createActivityRecord(oldTask); 83 final ActivityRecord opening = createActivityRecord(newTask); 84 // Start states. 85 changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 86 changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */)); 87 changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 88 changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */)); 89 fillChangeMap(changes, newTask); 90 // End states. 91 closing.mVisibleRequested = false; 92 opening.mVisibleRequested = true; 93 94 final int transit = transition.mType; 95 int flags = 0; 96 97 // Check basic both tasks participating 98 participants.add(oldTask); 99 participants.add(newTask); 100 ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes); 101 TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes); 102 assertEquals(2, info.getChanges().size()); 103 assertEquals(transit, info.getType()); 104 105 // Check that children are pruned 106 participants.add(opening); 107 participants.add(closing); 108 targets = Transition.calculateTargets(participants, changes); 109 info = Transition.calculateTransitionInfo(transit, flags, targets, changes); 110 assertEquals(2, info.getChanges().size()); 111 assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); 112 assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); 113 114 // Check combined prune and promote 115 participants.remove(newTask); 116 targets = Transition.calculateTargets(participants, changes); 117 info = Transition.calculateTransitionInfo(transit, flags, targets, changes); 118 assertEquals(2, info.getChanges().size()); 119 assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); 120 assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); 121 122 // Check multi promote 123 participants.remove(oldTask); 124 targets = Transition.calculateTargets(participants, changes); 125 info = Transition.calculateTransitionInfo(transit, flags, targets, changes); 126 assertEquals(2, info.getChanges().size()); 127 assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); 128 assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); 129 } 130 131 @Test testCreateInfo_NestedTasks()132 public void testCreateInfo_NestedTasks() { 133 final Transition transition = createTestTransition(TRANSIT_OPEN); 134 ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; 135 ArraySet<WindowContainer> participants = transition.mParticipants; 136 137 final Task newTask = createTask(mDisplayContent); 138 final Task newNestedTask = createTaskInRootTask(newTask, 0); 139 final Task newNestedTask2 = createTaskInRootTask(newTask, 0); 140 final Task oldTask = createTask(mDisplayContent); 141 final ActivityRecord closing = createActivityRecord(oldTask); 142 final ActivityRecord opening = createActivityRecord(newNestedTask); 143 final ActivityRecord opening2 = createActivityRecord(newNestedTask2); 144 // Start states. 145 changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 146 changes.put(newNestedTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 147 changes.put(newNestedTask2, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 148 changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */)); 149 changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 150 changes.put(opening2, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 151 changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */)); 152 fillChangeMap(changes, newTask); 153 // End states. 154 closing.mVisibleRequested = false; 155 opening.mVisibleRequested = true; 156 opening2.mVisibleRequested = true; 157 158 final int transit = transition.mType; 159 int flags = 0; 160 161 // Check full promotion from leaf 162 participants.add(oldTask); 163 participants.add(opening); 164 participants.add(opening2); 165 ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes); 166 TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes); 167 assertEquals(2, info.getChanges().size()); 168 assertEquals(transit, info.getType()); 169 assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken())); 170 assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); 171 172 // Check that unchanging but visible descendant of sibling prevents promotion 173 participants.remove(opening2); 174 targets = Transition.calculateTargets(participants, changes); 175 info = Transition.calculateTransitionInfo(transit, flags, targets, changes); 176 assertEquals(2, info.getChanges().size()); 177 assertNotNull(info.getChange(newNestedTask.mRemoteToken.toWindowContainerToken())); 178 assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken())); 179 } 180 181 @Test testCreateInfo_DisplayArea()182 public void testCreateInfo_DisplayArea() { 183 final Transition transition = createTestTransition(TRANSIT_OPEN); 184 ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; 185 ArraySet<WindowContainer> participants = transition.mParticipants; 186 final Task showTask = createTask(mDisplayContent); 187 final Task showNestedTask = createTaskInRootTask(showTask, 0); 188 final Task showTask2 = createTask(mDisplayContent); 189 final DisplayArea tda = showTask.getDisplayArea(); 190 final ActivityRecord showing = createActivityRecord(showNestedTask); 191 final ActivityRecord showing2 = createActivityRecord(showTask2); 192 // Start states. 193 changes.put(showTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 194 changes.put(showNestedTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 195 changes.put(showTask2, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 196 changes.put(tda, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 197 changes.put(showing, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 198 changes.put(showing2, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 199 fillChangeMap(changes, tda); 200 201 // End states. 202 showing.mVisibleRequested = true; 203 showing2.mVisibleRequested = true; 204 205 final int transit = transition.mType; 206 int flags = 0; 207 208 // Check promotion to DisplayArea 209 participants.add(showing); 210 participants.add(showing2); 211 ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes); 212 TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes); 213 assertEquals(1, info.getChanges().size()); 214 assertEquals(transit, info.getType()); 215 assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken())); 216 217 ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); 218 // Check that organized tasks get reported even if not top 219 showTask.mTaskOrganizer = mockOrg; 220 targets = Transition.calculateTargets(participants, changes); 221 info = Transition.calculateTransitionInfo(transit, flags, targets, changes); 222 assertEquals(2, info.getChanges().size()); 223 assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken())); 224 assertNotNull(info.getChange(showTask.mRemoteToken.toWindowContainerToken())); 225 // Even if DisplayArea explicitly participating 226 participants.add(tda); 227 targets = Transition.calculateTargets(participants, changes); 228 info = Transition.calculateTransitionInfo(transit, flags, targets, changes); 229 assertEquals(2, info.getChanges().size()); 230 } 231 232 @Test testCreateInfo_existenceChange()233 public void testCreateInfo_existenceChange() { 234 final Transition transition = createTestTransition(TRANSIT_OPEN); 235 236 final Task openTask = createTask(mDisplayContent); 237 final ActivityRecord opening = createActivityRecord(openTask); 238 opening.mVisibleRequested = false; // starts invisible 239 final Task closeTask = createTask(mDisplayContent); 240 final ActivityRecord closing = createActivityRecord(closeTask); 241 closing.mVisibleRequested = true; // starts visible 242 243 transition.collectExistenceChange(openTask); 244 transition.collect(opening); 245 transition.collect(closing); 246 opening.mVisibleRequested = true; 247 closing.mVisibleRequested = false; 248 249 ArraySet<WindowContainer> targets = Transition.calculateTargets( 250 transition.mParticipants, transition.mChanges); 251 TransitionInfo info = Transition.calculateTransitionInfo( 252 0, 0, targets, transition.mChanges); 253 assertEquals(2, info.getChanges().size()); 254 // There was an existence change on open, so it should be OPEN rather than SHOW 255 assertEquals(TRANSIT_OPEN, 256 info.getChange(openTask.mRemoteToken.toWindowContainerToken()).getMode()); 257 // No exestence change on closing, so HIDE rather than CLOSE 258 assertEquals(TRANSIT_TO_BACK, 259 info.getChange(closeTask.mRemoteToken.toWindowContainerToken()).getMode()); 260 } 261 262 @Test testCreateInfo_ordering()263 public void testCreateInfo_ordering() { 264 final Transition transition = createTestTransition(TRANSIT_OPEN); 265 // pick some number with a high enough chance of being out-of-order when added to set. 266 final int taskCount = 6; 267 268 final Task[] tasks = new Task[taskCount]; 269 for (int i = 0; i < taskCount; ++i) { 270 // Each add goes on top, so at the end of this, task[9] should be on top 271 tasks[i] = createTask(mDisplayContent, 272 WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); 273 final ActivityRecord act = createActivityRecord(tasks[i]); 274 // alternate so that the transition doesn't get promoted to the display area 275 act.mVisibleRequested = (i % 2) == 0; // starts invisible 276 } 277 278 // doesn't matter which order collected since participants is a set 279 for (int i = 0; i < taskCount; ++i) { 280 transition.collectExistenceChange(tasks[i]); 281 final ActivityRecord act = tasks[i].getTopMostActivity(); 282 transition.collect(act); 283 tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0; 284 } 285 286 ArraySet<WindowContainer> targets = Transition.calculateTargets( 287 transition.mParticipants, transition.mChanges); 288 TransitionInfo info = Transition.calculateTransitionInfo( 289 0, 0, targets, transition.mChanges); 290 assertEquals(taskCount, info.getChanges().size()); 291 // verify order is top-to-bottem 292 for (int i = 0; i < taskCount; ++i) { 293 assertEquals(tasks[taskCount - i - 1].mRemoteToken.toWindowContainerToken(), 294 info.getChanges().get(i).getContainer()); 295 } 296 } 297 298 @Test testCreateInfo_wallpaper()299 public void testCreateInfo_wallpaper() { 300 final Transition transition = createTestTransition(TRANSIT_OPEN); 301 // pick some number with a high enough chance of being out-of-order when added to set. 302 final int taskCount = 4; 303 final int showWallpaperTask = 2; 304 305 final Task[] tasks = new Task[taskCount]; 306 for (int i = 0; i < taskCount; ++i) { 307 // Each add goes on top, so at the end of this, task[9] should be on top 308 tasks[i] = createTask(mDisplayContent, 309 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); 310 final ActivityRecord act = createActivityRecord(tasks[i]); 311 // alternate so that the transition doesn't get promoted to the display area 312 act.mVisibleRequested = (i % 2) == 0; // starts invisible 313 if (i == showWallpaperTask) { 314 doReturn(true).when(act).showWallpaper(); 315 } 316 } 317 318 final WallpaperWindowToken wallpaperWindowToken = spy(new WallpaperWindowToken(mWm, 319 mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */)); 320 final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken, 321 "wallpaperWindow"); 322 wallpaperWindowToken.setVisibleRequested(false); 323 transition.collect(wallpaperWindowToken); 324 wallpaperWindowToken.setVisibleRequested(true); 325 wallpaperWindow.mHasSurface = true; 326 327 // doesn't matter which order collected since participants is a set 328 for (int i = 0; i < taskCount; ++i) { 329 transition.collectExistenceChange(tasks[i]); 330 final ActivityRecord act = tasks[i].getTopMostActivity(); 331 transition.collect(act); 332 tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0; 333 } 334 335 ArraySet<WindowContainer> targets = Transition.calculateTargets( 336 transition.mParticipants, transition.mChanges); 337 TransitionInfo info = Transition.calculateTransitionInfo( 338 0, 0, targets, transition.mChanges); 339 // verify that wallpaper is at bottom 340 assertEquals(taskCount + 1, info.getChanges().size()); 341 // The wallpaper is not organized, so it won't have a token; however, it will be marked 342 // as IS_WALLPAPER 343 assertEquals(FLAG_IS_WALLPAPER, 344 info.getChanges().get(info.getChanges().size() - 1).getFlags()); 345 assertEquals(FLAG_SHOW_WALLPAPER, info.getChange( 346 tasks[showWallpaperTask].mRemoteToken.toWindowContainerToken()).getFlags()); 347 } 348 349 @Test testTargets_noIntermediatesToWallpaper()350 public void testTargets_noIntermediatesToWallpaper() { 351 final Transition transition = createTestTransition(TRANSIT_OPEN); 352 353 final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, 354 mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */); 355 // Make DA organized so we can check that they don't get included. 356 WindowContainer parent = wallpaperWindowToken.getParent(); 357 while (parent != null && parent != mDisplayContent) { 358 if (parent.asDisplayArea() != null) { 359 parent.asDisplayArea().setOrganizer( 360 mock(android.window.IDisplayAreaOrganizer.class), true /* skipAppear */); 361 } 362 parent = parent.getParent(); 363 } 364 final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken, 365 "wallpaperWindow"); 366 wallpaperWindowToken.setVisibleRequested(false); 367 transition.collect(wallpaperWindowToken); 368 wallpaperWindowToken.setVisibleRequested(true); 369 wallpaperWindow.mHasSurface = true; 370 doReturn(true).when(mDisplayContent).isAttached(); 371 transition.collect(mDisplayContent); 372 mDisplayContent.getWindowConfiguration().setRotation( 373 (mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4); 374 375 ArraySet<WindowContainer> targets = Transition.calculateTargets( 376 transition.mParticipants, transition.mChanges); 377 TransitionInfo info = Transition.calculateTransitionInfo( 378 0, 0, targets, transition.mChanges); 379 // The wallpaper is not organized, so it won't have a token; however, it will be marked 380 // as IS_WALLPAPER 381 assertEquals(FLAG_IS_WALLPAPER, info.getChanges().get(0).getFlags()); 382 // Make sure no intermediate display areas were pulled in between wallpaper and display. 383 assertEquals(mDisplayContent.mRemoteToken.toWindowContainerToken(), 384 info.getChanges().get(0).getParent()); 385 } 386 387 @Test testIndependent()388 public void testIndependent() { 389 final Transition transition = createTestTransition(TRANSIT_OPEN); 390 ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges; 391 ArraySet<WindowContainer> participants = transition.mParticipants; 392 ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); 393 394 final Task openTask = createTask(mDisplayContent); 395 final Task openInOpenTask = createTaskInRootTask(openTask, 0); 396 final ActivityRecord openInOpen = createActivityRecord(openInOpenTask); 397 398 final Task changeTask = createTask(mDisplayContent); 399 final Task changeInChangeTask = createTaskInRootTask(changeTask, 0); 400 final Task openInChangeTask = createTaskInRootTask(changeTask, 0); 401 final ActivityRecord changeInChange = createActivityRecord(changeInChangeTask); 402 final ActivityRecord openInChange = createActivityRecord(openInChangeTask); 403 // set organizer for everything so that they all get added to transition info 404 for (Task t : new Task[]{ 405 openTask, openInOpenTask, changeTask, changeInChangeTask, openInChangeTask}) { 406 t.mTaskOrganizer = mockOrg; 407 } 408 409 // Start states. 410 changes.put(openTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 411 changes.put(changeTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); 412 changes.put(openInOpenTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 413 changes.put(openInChangeTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 414 changes.put(changeInChangeTask, 415 new Transition.ChangeInfo(true /* vis */, false /* exChg */)); 416 changes.put(openInOpen, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 417 changes.put(openInChange, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); 418 changes.put(changeInChange, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); 419 fillChangeMap(changes, openTask); 420 // End states. 421 changeInChange.mVisibleRequested = true; 422 openInOpen.mVisibleRequested = true; 423 openInChange.mVisibleRequested = true; 424 425 final int transit = transition.mType; 426 int flags = 0; 427 428 // Check full promotion from leaf 429 participants.add(changeInChange); 430 participants.add(openInOpen); 431 participants.add(openInChange); 432 // Explicitly add changeTask (to test independence with parents) 433 participants.add(changeTask); 434 ArraySet<WindowContainer> targets = Transition.calculateTargets(participants, changes); 435 TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes); 436 // Root changes should always be considered independent 437 assertTrue(isIndependent( 438 info.getChange(openTask.mRemoteToken.toWindowContainerToken()), info)); 439 assertTrue(isIndependent( 440 info.getChange(changeTask.mRemoteToken.toWindowContainerToken()), info)); 441 442 // Children of a open/close change are not independent 443 assertFalse(isIndependent( 444 info.getChange(openInOpenTask.mRemoteToken.toWindowContainerToken()), info)); 445 446 // Non-root changes are not independent 447 assertFalse(isIndependent( 448 info.getChange(changeInChangeTask.mRemoteToken.toWindowContainerToken()), info)); 449 450 // open/close within a change are independent 451 assertTrue(isIndependent( 452 info.getChange(openInChangeTask.mRemoteToken.toWindowContainerToken()), info)); 453 } 454 455 @Test testTimeout()456 public void testTimeout() { 457 final TransitionController controller = new TransitionController(mAtm, 458 mock(TaskSnapshotController.class)); 459 final BLASTSyncEngine sync = new BLASTSyncEngine(mWm); 460 final CountDownLatch latch = new CountDownLatch(1); 461 // When the timeout is reached, it will finish the sync-group and notify transaction ready. 462 new Transition(TRANSIT_OPEN, 0 /* flags */, 10 /* timeoutMs */, controller, sync) { 463 @Override 464 public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) { 465 latch.countDown(); 466 } 467 }; 468 assertTrue(awaitInWmLock(() -> latch.await(3, TimeUnit.SECONDS))); 469 } 470 471 @Test testIntermediateVisibility()472 public void testIntermediateVisibility() { 473 final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class); 474 final TransitionController controller = new TransitionController(mAtm, snapshotController); 475 final ITransitionPlayer player = new ITransitionPlayer.Default(); 476 controller.registerTransitionPlayer(player, null /* appThread */); 477 ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); 478 final Transition openTransition = controller.createTransition(TRANSIT_OPEN); 479 480 // Start out with task2 visible and set up a transition that closes task2 and opens task1 481 final Task task1 = createTask(mDisplayContent); 482 task1.mTaskOrganizer = mockOrg; 483 final ActivityRecord activity1 = createActivityRecord(task1); 484 activity1.mVisibleRequested = false; 485 activity1.setVisible(false); 486 final Task task2 = createTask(mDisplayContent); 487 task2.mTaskOrganizer = mockOrg; 488 final ActivityRecord activity2 = createActivityRecord(task1); 489 activity2.mVisibleRequested = true; 490 activity2.setVisible(true); 491 492 openTransition.collectExistenceChange(task1); 493 openTransition.collectExistenceChange(activity1); 494 openTransition.collectExistenceChange(task2); 495 openTransition.collectExistenceChange(activity2); 496 497 activity1.mVisibleRequested = true; 498 activity1.setVisible(true); 499 activity2.mVisibleRequested = false; 500 501 // Using abort to force-finish the sync (since we can't wait for drawing in unit test). 502 // We didn't call abort on the transition itself, so it will still run onTransactionReady 503 // normally. 504 mWm.mSyncEngine.abort(openTransition.getSyncId()); 505 506 // Before finishing openTransition, we are now going to simulate closing task1 to return 507 // back to (open) task2. 508 final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE); 509 510 closeTransition.collectExistenceChange(task1); 511 closeTransition.collectExistenceChange(activity1); 512 closeTransition.collectExistenceChange(task2); 513 closeTransition.collectExistenceChange(activity2); 514 515 activity1.mVisibleRequested = false; 516 activity2.mVisibleRequested = true; 517 518 openTransition.finishTransition(); 519 520 // We finished the openTransition. Even though activity1 is visibleRequested=false, since 521 // the closeTransition animation hasn't played yet, make sure that we didn't commit 522 // visible=false on activity1 since it needs to remain visible for the animation. 523 assertTrue(activity1.isVisible()); 524 assertTrue(activity2.isVisible()); 525 526 // Using abort to force-finish the sync (since we obviously can't wait for drawing). 527 // We didn't call abort on the actual transition, so it will still run onTransactionReady 528 // normally. 529 mWm.mSyncEngine.abort(closeTransition.getSyncId()); 530 531 closeTransition.finishTransition(); 532 533 assertFalse(activity1.isVisible()); 534 assertTrue(activity2.isVisible()); 535 } 536 537 @Test testTransientLaunch()538 public void testTransientLaunch() { 539 final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class); 540 final TransitionController controller = new TransitionController(mAtm, snapshotController); 541 final ITransitionPlayer player = new ITransitionPlayer.Default(); 542 controller.registerTransitionPlayer(player, null /* appThread */); 543 ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); 544 final Transition openTransition = controller.createTransition(TRANSIT_OPEN); 545 546 // Start out with task2 visible and set up a transition that closes task2 and opens task1 547 final Task task1 = createTask(mDisplayContent); 548 task1.mTaskOrganizer = mockOrg; 549 final ActivityRecord activity1 = createActivityRecord(task1); 550 activity1.mVisibleRequested = false; 551 activity1.setVisible(false); 552 final Task task2 = createTask(mDisplayContent); 553 task2.mTaskOrganizer = mockOrg; 554 final ActivityRecord activity2 = createActivityRecord(task2); 555 activity2.mVisibleRequested = true; 556 activity2.setVisible(true); 557 558 openTransition.collectExistenceChange(task1); 559 openTransition.collectExistenceChange(activity1); 560 openTransition.collectExistenceChange(task2); 561 openTransition.collectExistenceChange(activity2); 562 563 activity1.mVisibleRequested = true; 564 activity1.setVisible(true); 565 activity2.mVisibleRequested = false; 566 567 // Using abort to force-finish the sync (since we can't wait for drawing in unit test). 568 // We didn't call abort on the transition itself, so it will still run onTransactionReady 569 // normally. 570 mWm.mSyncEngine.abort(openTransition.getSyncId()); 571 572 verify(snapshotController, times(1)).recordTaskSnapshot(eq(task2), eq(false)); 573 574 openTransition.finishTransition(); 575 576 // We are now going to simulate closing task1 to return back to (open) task2. 577 final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE); 578 579 closeTransition.collectExistenceChange(task1); 580 closeTransition.collectExistenceChange(activity1); 581 closeTransition.collectExistenceChange(task2); 582 closeTransition.collectExistenceChange(activity2); 583 closeTransition.setTransientLaunch(activity2); 584 585 activity1.mVisibleRequested = false; 586 activity2.mVisibleRequested = true; 587 588 // Using abort to force-finish the sync (since we obviously can't wait for drawing). 589 // We didn't call abort on the actual transition, so it will still run onTransactionReady 590 // normally. 591 mWm.mSyncEngine.abort(closeTransition.getSyncId()); 592 593 // Make sure we haven't called recordSnapshot (since we are transient, it shouldn't be 594 // called until finish). 595 verify(snapshotController, times(0)).recordTaskSnapshot(eq(task1), eq(false)); 596 597 closeTransition.finishTransition(); 598 599 verify(snapshotController, times(1)).recordTaskSnapshot(eq(task1), eq(false)); 600 } 601 602 /** Fill the change map with all the parents of top. Change maps are usually fully populated */ fillChangeMap(ArrayMap<WindowContainer, Transition.ChangeInfo> changes, WindowContainer top)603 private static void fillChangeMap(ArrayMap<WindowContainer, Transition.ChangeInfo> changes, 604 WindowContainer top) { 605 for (WindowContainer curr = top.getParent(); curr != null; curr = curr.getParent()) { 606 changes.put(curr, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); 607 } 608 } 609 } 610