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