1 /*
2  * Copyright (C) 2018 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.content.res.Configuration.ORIENTATION_PORTRAIT;
20 import static android.view.Display.DEFAULT_DISPLAY;
21 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
22 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
23 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
24 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
25 
26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
27 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
29 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
30 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
32 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
33 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
34 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
35 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
36 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
37 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
38 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
39 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM;
40 
41 import static org.junit.Assert.assertEquals;
42 import static org.junit.Assert.assertFalse;
43 import static org.junit.Assert.assertNotEquals;
44 import static org.junit.Assert.assertNotNull;
45 import static org.junit.Assert.assertTrue;
46 import static org.junit.Assert.fail;
47 import static org.mockito.ArgumentMatchers.any;
48 import static org.mockito.ArgumentMatchers.anyBoolean;
49 import static org.mockito.ArgumentMatchers.anyInt;
50 import static org.mockito.ArgumentMatchers.eq;
51 import static org.mockito.Mockito.clearInvocations;
52 import static org.mockito.Mockito.mock;
53 import static org.mockito.Mockito.never;
54 import static org.mockito.Mockito.reset;
55 
56 import android.content.pm.ActivityInfo;
57 import android.content.res.Configuration;
58 import android.os.Binder;
59 import android.os.IBinder;
60 import android.os.IInterface;
61 import android.platform.test.annotations.Presubmit;
62 import android.util.SparseBooleanArray;
63 import android.view.IRecentsAnimationRunner;
64 import android.view.SurfaceControl;
65 import android.view.WindowManager.LayoutParams;
66 import android.window.TaskSnapshot;
67 
68 import androidx.test.filters.SmallTest;
69 
70 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
71 
72 import com.google.common.truth.Truth;
73 
74 import org.junit.Before;
75 import org.junit.Test;
76 import org.junit.runner.RunWith;
77 import org.mockito.Mock;
78 import org.mockito.MockitoAnnotations;
79 
80 import java.util.ArrayList;
81 
82 /**
83  * Build/Install/Run:
84  *  atest WmTests:RecentsAnimationControllerTest
85  */
86 @SmallTest
87 @Presubmit
88 @RunWith(WindowTestRunner.class)
89 public class RecentsAnimationControllerTest extends WindowTestsBase {
90 
91     @Mock SurfaceControl mMockLeash;
92     @Mock SurfaceControl.Transaction mMockTransaction;
93     @Mock OnAnimationFinishedCallback mFinishedCallback;
94     @Mock IRecentsAnimationRunner mMockRunner;
95     @Mock RecentsAnimationController.RecentsAnimationCallbacks mAnimationCallbacks;
96     @Mock TaskSnapshot mMockTaskSnapshot;
97     private RecentsAnimationController mController;
98     private Task mRootHomeTask;
99 
100     @Before
setUp()101     public void setUp() throws Exception {
102         MockitoAnnotations.initMocks(this);
103         doNothing().when(mWm.mRoot).performSurfacePlacement();
104         when(mMockRunner.asBinder()).thenReturn(new Binder());
105         mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
106                 DEFAULT_DISPLAY));
107         mRootHomeTask = mDefaultDisplay.getDefaultTaskDisplayArea().getRootHomeTask();
108         assertNotNull(mRootHomeTask);
109     }
110 
111     @Test
testRemovedBeforeStarted_expectCanceled()112     public void testRemovedBeforeStarted_expectCanceled() throws Exception {
113         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
114         AnimationAdapter adapter = mController.addAnimation(activity.getTask(),
115                 false /* isRecentTaskInvisible */);
116         adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS,
117                 mFinishedCallback);
118 
119         // The activity doesn't contain window so the animation target cannot be created.
120         mController.startAnimation();
121 
122         // Verify that the finish callback to reparent the leash is called
123         verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), eq(adapter));
124         // Verify the animation canceled callback to the app was made
125         verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */);
126         verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
127     }
128 
129     @Test
testCancelAfterRemove_expectIgnored()130     public void testCancelAfterRemove_expectIgnored() {
131         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
132         AnimationAdapter adapter = mController.addAnimation(activity.getTask(),
133                 false /* isRecentTaskInvisible */);
134         adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS,
135                 mFinishedCallback);
136 
137         // Remove the app window so that the animation target can not be created
138         activity.removeImmediately();
139         mController.startAnimation();
140         mController.cleanupAnimation(REORDER_KEEP_IN_PLACE);
141         try {
142             mController.cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "test");
143         } catch (Exception e) {
144             fail("Unexpected failure when canceling animation after finishing it");
145         }
146     }
147 
148     @Test
testIncludedApps_expectTargetAndVisible()149     public void testIncludedApps_expectTargetAndVisible() {
150         mWm.setRecentsAnimationController(mController);
151         final ActivityRecord homeActivity = createHomeActivity();
152         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
153         final ActivityRecord hiddenActivity = createActivityRecord(mDefaultDisplay);
154         hiddenActivity.setVisible(false);
155         mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
156                 mDefaultDisplay.getRotation());
157         initializeRecentsAnimationController(mController, homeActivity);
158 
159         // Ensure that we are animating the target activity as well
160         assertTrue(mController.isAnimatingTask(homeActivity.getTask()));
161         assertTrue(mController.isAnimatingTask(activity.getTask()));
162         assertFalse(mController.isAnimatingTask(hiddenActivity.getTask()));
163     }
164 
165     @Test
testLaunchAndStartRecents_expectTargetAndVisible()166     public void testLaunchAndStartRecents_expectTargetAndVisible() throws Exception {
167         mWm.setRecentsAnimationController(mController);
168         final ActivityRecord homeActivity = createHomeActivity();
169         final Task task = createTask(mDefaultDisplay);
170         // Emulate that activity1 has just launched activity2, but app transition has not yet been
171         // executed.
172         final ActivityRecord activity1 = createActivityRecord(task);
173         activity1.setVisible(true);
174         activity1.setVisibleRequested(false);
175         activity1.addWindow(createWindowState(new LayoutParams(TYPE_BASE_APPLICATION), activity1));
176 
177         final ActivityRecord activity2 = createActivityRecord(task);
178         activity2.setVisible(false);
179         activity2.setVisibleRequested(true);
180 
181         mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
182                 mDefaultDisplay.getRotation());
183         initializeRecentsAnimationController(mController, homeActivity);
184         mController.startAnimation();
185         verify(mMockRunner, never()).onAnimationCanceled(null /* taskIds */,
186                 null /* taskSnapshots */);
187     }
188 
189     @Test
testWallpaperIncluded_expectTarget()190     public void testWallpaperIncluded_expectTarget() throws Exception {
191         mWm.setRecentsAnimationController(mController);
192         final ActivityRecord homeActivity = createHomeActivity();
193         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
194         final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
195         activity.addWindow(win1);
196         final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
197                 mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */);
198         spyOn(mDefaultDisplay.mWallpaperController);
199         doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible();
200 
201         mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
202                 mDefaultDisplay.getRotation());
203         initializeRecentsAnimationController(mController, homeActivity);
204         mController.startAnimation();
205 
206         // Ensure that we are animating the app and wallpaper target
207         assertTrue(mController.isAnimatingTask(activity.getTask()));
208         assertTrue(mController.isAnimatingWallpaper(wallpaperWindowToken));
209     }
210 
211     @Test
testWallpaperAnimatorCanceled_expectAnimationKeepsRunning()212     public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception {
213         mWm.setRecentsAnimationController(mController);
214         final ActivityRecord homeActivity = createHomeActivity();
215         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
216         final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
217         activity.addWindow(win1);
218         final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
219                 mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */);
220         spyOn(mDefaultDisplay.mWallpaperController);
221         doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible();
222 
223         mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
224                 mDefaultDisplay.getRotation());
225         initializeRecentsAnimationController(mController, homeActivity);
226         mController.startAnimation();
227 
228         // Cancel the animation and ensure the controller is still running
229         wallpaperWindowToken.cancelAnimation();
230         assertTrue(mController.isAnimatingTask(activity.getTask()));
231         assertFalse(mController.isAnimatingWallpaper(wallpaperWindowToken));
232         verify(mMockRunner, never()).onAnimationCanceled(null /* taskIds */,
233                 null /* taskSnapshots */);
234     }
235 
236     @Test
testFinish_expectTargetAndWallpaperAdaptersRemoved()237     public void testFinish_expectTargetAndWallpaperAdaptersRemoved() {
238         mWm.setRecentsAnimationController(mController);
239         final ActivityRecord homeActivity = createHomeActivity();
240         final WindowState hwin1 = createWindow(null, TYPE_BASE_APPLICATION, homeActivity, "hwin1");
241         homeActivity.addWindow(hwin1);
242         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
243         final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
244         activity.addWindow(win1);
245         final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
246                 mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */);
247         spyOn(mDefaultDisplay.mWallpaperController);
248         doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible();
249 
250         // Start and finish the animation
251         initializeRecentsAnimationController(mController, homeActivity);
252         mController.startAnimation();
253 
254         assertTrue(mController.isAnimatingTask(homeActivity.getTask()));
255         assertTrue(mController.isAnimatingTask(activity.getTask()));
256 
257         // Reset at this point since we may remove adapters that couldn't be created
258         clearInvocations(mController);
259         mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
260 
261         // Ensure that we remove the task (home & app) and wallpaper adapters
262         verify(mController, times(2)).removeAnimation(any());
263         verify(mController, times(1)).removeWallpaperAnimation(any());
264     }
265 
266     @Test
testDeferCancelAnimation()267     public void testDeferCancelAnimation() throws Exception {
268         mWm.setRecentsAnimationController(mController);
269         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
270         final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
271         activity.addWindow(win1);
272         assertEquals(activity.getTask().getTopVisibleActivity(), activity);
273         assertEquals(activity.findMainWindow(), win1);
274 
275         mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */);
276         assertTrue(mController.isAnimatingTask(activity.getTask()));
277 
278         mController.setDeferredCancel(true /* deferred */, false /* screenshot */);
279         mController.cancelAnimationWithScreenshot(false /* screenshot */);
280         verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */);
281 
282         // Simulate the app transition finishing
283         mController.mAppTransitionListener.onAppTransitionStartingLocked(0, 0);
284         verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
285     }
286 
287     @Test
testDeferCancelAnimationWithScreenShot()288     public void testDeferCancelAnimationWithScreenShot() throws Exception {
289         mWm.setRecentsAnimationController(mController);
290         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
291         final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
292         activity.addWindow(win1);
293         assertEquals(activity.getTask().getTopVisibleActivity(), activity);
294         assertEquals(activity.findMainWindow(), win1);
295 
296         RecentsAnimationController.TaskAnimationAdapter adapter = mController.addAnimation(
297                 activity.getTask(), false /* isRecentTaskInvisible */);
298         assertTrue(mController.isAnimatingTask(activity.getTask()));
299 
300         spyOn(mWm.mTaskSnapshotController);
301         doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(),
302                 anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */);
303         mController.setDeferredCancel(true /* deferred */, true /* screenshot */);
304         mController.cancelAnimationWithScreenshot(true /* screenshot */);
305         verify(mMockRunner).onAnimationCanceled(any(int[].class) /* taskIds */,
306                 any(TaskSnapshot[].class) /* taskSnapshots */);
307 
308         // Continue the animation (simulating a call to cleanupScreenshot())
309         mController.continueDeferredCancelAnimation();
310         verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
311     }
312 
313     @Test
testShouldAnimateWhenNoCancelWithDeferredScreenshot()314     public void testShouldAnimateWhenNoCancelWithDeferredScreenshot() {
315         mWm.setRecentsAnimationController(mController);
316         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
317         final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
318         activity.addWindow(win1);
319         assertEquals(activity.getTask().getTopVisibleActivity(), activity);
320         assertEquals(activity.findMainWindow(), win1);
321 
322         mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */);
323         assertTrue(mController.isAnimatingTask(activity.getTask()));
324 
325         // Assume activity transition should animate when no
326         // IRecentsAnimationController#setDeferCancelUntilNextTransition called.
327         assertFalse(mController.shouldDeferCancelWithScreenshot());
328         assertTrue(activity.shouldAnimate());
329     }
330 
331     @Test
testBinderDiedAfterCancelWithDeferredScreenshot()332     public void testBinderDiedAfterCancelWithDeferredScreenshot() throws Exception {
333         mWm.setRecentsAnimationController(mController);
334         final ActivityRecord homeActivity = createHomeActivity();
335         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
336         final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
337         activity.addWindow(win1);
338 
339         initializeRecentsAnimationController(mController, homeActivity);
340         mController.setWillFinishToHome(true);
341 
342         // Verify cancel is called with a snapshot and that we've created an overlay
343         spyOn(mWm.mTaskSnapshotController);
344         doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(),
345                 anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */);
346         mController.cancelAnimationWithScreenshot(true /* screenshot */);
347         verify(mMockRunner).onAnimationCanceled(any(), any());
348 
349         // Simulate process crashing and ensure the animation is still canceled
350         mController.binderDied();
351         verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
352     }
353 
354     @Test
testRecentViewInFixedPortraitWhenTopAppInLandscape()355     public void testRecentViewInFixedPortraitWhenTopAppInLandscape() {
356         unblockDisplayRotation(mDefaultDisplay);
357         mWm.setRecentsAnimationController(mController);
358 
359         final ActivityRecord homeActivity = createHomeActivity();
360         homeActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
361 
362         final ActivityRecord landActivity = createActivityRecord(mDefaultDisplay);
363         landActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
364         final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, landActivity, "win1");
365         landActivity.addWindow(win1);
366 
367         assertEquals(landActivity.getTask().getTopVisibleActivity(), landActivity);
368         assertEquals(landActivity.findMainWindow(), win1);
369 
370         // Ensure that the display is in Landscape
371         landActivity.onDescendantOrientationChanged(landActivity);
372         assertEquals(Configuration.ORIENTATION_LANDSCAPE,
373                 mDefaultDisplay.getConfiguration().orientation);
374 
375         initializeRecentsAnimationController(mController, homeActivity);
376 
377         assertTrue(mDefaultDisplay.isFixedRotationLaunchingApp(homeActivity));
378 
379         // Check that the home app is in portrait
380         assertEquals(Configuration.ORIENTATION_PORTRAIT,
381                 homeActivity.getConfiguration().orientation);
382 
383         // Home activity won't become top (return to landActivity), so the top rotated record should
384         // be cleared.
385         mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
386         assertFalse(mDefaultDisplay.isFixedRotationLaunchingApp(homeActivity));
387         assertFalse(mDefaultDisplay.hasTopFixedRotationLaunchingApp());
388         // The transform should keep until the transition is done, so the restored configuration
389         // won't be sent to activity and cause unnecessary configuration change.
390         assertTrue(homeActivity.hasFixedRotationTransform());
391 
392         // In real case the transition will be executed from RecentsAnimation#finishAnimation.
393         mDefaultDisplay.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(
394                 homeActivity.token);
395         assertFalse(homeActivity.hasFixedRotationTransform());
396     }
397 
prepareFixedRotationLaunchingAppWithRecentsAnim()398     private ActivityRecord prepareFixedRotationLaunchingAppWithRecentsAnim() {
399         final ActivityRecord homeActivity = createHomeActivity();
400         homeActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
401         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
402         // Add a window so it can be animated by the recents.
403         final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
404         activity.addWindow(win);
405         // Assume an activity is launching to different rotation.
406         mDefaultDisplay.setFixedRotationLaunchingApp(activity,
407                 (mDefaultDisplay.getRotation() + 1) % 4);
408 
409         assertTrue(activity.hasFixedRotationTransform());
410         assertTrue(mDefaultDisplay.isFixedRotationLaunchingApp(activity));
411 
412         // Before the transition is done, the recents animation is triggered.
413         initializeRecentsAnimationController(mController, homeActivity);
414         assertFalse(homeActivity.hasFixedRotationTransform());
415         assertTrue(mController.isAnimatingTask(activity.getTask()));
416 
417         return activity;
418     }
419 
420     @Test
testClearFixedRotationLaunchingAppAfterCleanupAnimation()421     public void testClearFixedRotationLaunchingAppAfterCleanupAnimation() {
422         final ActivityRecord activity = prepareFixedRotationLaunchingAppWithRecentsAnim();
423 
424         // Simulate giving up the swipe up gesture to keep the original activity as top.
425         mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
426         // The rotation transform should be cleared after updating orientation with display.
427         assertTopFixedRotationLaunchingAppCleared(activity);
428 
429         // Simulate swiping up recents (home) in different rotation.
430         final ActivityRecord home = mDefaultDisplay.getDefaultTaskDisplayArea().getHomeActivity();
431         startRecentsInDifferentRotation(home);
432 
433         // If the recents activity becomes the top running activity (e.g. the original top activity
434         // is either finishing or moved to back during recents animation), the display orientation
435         // will be determined by it so the fixed rotation must be cleared.
436         activity.finishing = true;
437         mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
438         assertTopFixedRotationLaunchingAppCleared(home);
439 
440         startRecentsInDifferentRotation(home);
441         // Assume recents activity becomes invisible for some reason (e.g. screen off).
442         home.setVisible(false);
443         mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
444         // Although there won't be a transition finish callback, the fixed rotation must be cleared.
445         assertTopFixedRotationLaunchingAppCleared(home);
446     }
447 
448     @Test
testKeepFixedRotationWhenMovingRecentsToTop()449     public void testKeepFixedRotationWhenMovingRecentsToTop() {
450         final ActivityRecord activity = prepareFixedRotationLaunchingAppWithRecentsAnim();
451         // Assume a transition animation has started running before recents animation. Then the
452         // activity will receive onAnimationFinished that notifies app transition finished when
453         // removing the recents animation of task.
454         activity.getTask().getAnimationSources().add(activity);
455 
456         // Simulate swiping to home/recents before the transition is done.
457         mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
458         // The rotation transform should be preserved. In real case, it will be cleared by the next
459         // move-to-top transition.
460         assertTrue(activity.hasFixedRotationTransform());
461     }
462 
463     @Test
testCheckRotationAfterCleanup()464     public void testCheckRotationAfterCleanup() {
465         mWm.setRecentsAnimationController(mController);
466         spyOn(mDisplayContent.mFixedRotationTransitionListener);
467         final ActivityRecord recents = mock(ActivityRecord.class);
468         recents.setOverrideOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
469         doReturn(ORIENTATION_PORTRAIT).when(recents)
470                 .getRequestedConfigurationOrientation(anyBoolean());
471         mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recents);
472 
473         // Rotation update is skipped while the recents animation is running.
474         final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
475         final int topOrientation = DisplayContentTests.getRotatedOrientation(mDefaultDisplay);
476         assertFalse(displayRotation.updateOrientation(topOrientation, false /* forceUpdate */));
477         assertEquals(ActivityInfo.SCREEN_ORIENTATION_UNSET, displayRotation.getLastOrientation());
478         final int prevRotation = mDisplayContent.getRotation();
479         mWm.cleanupRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
480 
481         // In real case, it is called from RecentsAnimation#finishAnimation -> continueWindowLayout
482         // -> handleAppTransitionReady -> add FINISH_LAYOUT_REDO_CONFIG, and DisplayContent#
483         // applySurfaceChangesTransaction will call updateOrientation for FINISH_LAYOUT_REDO_CONFIG.
484         assertTrue(displayRotation.updateOrientation(topOrientation, false  /* forceUpdate */));
485         // The display should be updated to the changed orientation after the animation is finished.
486         assertNotEquals(displayRotation.getRotation(), prevRotation);
487     }
488 
489     @Test
testWallpaperHasFixedRotationApplied()490     public void testWallpaperHasFixedRotationApplied() {
491         unblockDisplayRotation(mDefaultDisplay);
492         mWm.setRecentsAnimationController(mController);
493 
494         // Create a portrait home activity, a wallpaper and a landscape activity displayed on top.
495         final ActivityRecord homeActivity = createHomeActivity();
496         homeActivity.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
497 
498         final WindowState homeWindow = createWindow(null, TYPE_BASE_APPLICATION, homeActivity,
499                 "homeWindow");
500         makeWindowVisible(homeWindow);
501         homeActivity.addWindow(homeWindow);
502         homeWindow.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
503 
504         // Landscape application
505         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
506         final WindowState applicationWindow = createWindow(null, TYPE_BASE_APPLICATION, activity,
507                 "applicationWindow");
508         activity.addWindow(applicationWindow);
509         activity.setOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
510 
511         // Wallpaper
512         final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
513                 mock(IBinder.class), true, mDefaultDisplay, true /* ownerCanManageAppTokens */);
514         final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
515                 "wallpaperWindow");
516 
517         // Make sure the landscape activity is on top and the display is in landscape
518         activity.moveFocusableActivityToTop("test");
519         mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
520                 mDefaultDisplay.getRotation());
521 
522         spyOn(mDefaultDisplay.mWallpaperController);
523         doReturn(true).when(mDefaultDisplay.mWallpaperController).isWallpaperVisible();
524 
525         // Start the recents animation
526         initializeRecentsAnimationController(mController, homeActivity);
527 
528         mDefaultDisplay.mWallpaperController.adjustWallpaperWindows();
529 
530         // Check preconditions
531         ArrayList<WallpaperWindowToken> wallpapers = new ArrayList<>(1);
532         mDefaultDisplay.forAllWallpaperWindows(wallpapers::add);
533 
534         Truth.assertThat(wallpapers).hasSize(1);
535         Truth.assertThat(wallpapers.get(0).getTopChild()).isEqualTo(wallpaperWindow);
536 
537         // Actual check
538         assertEquals(Configuration.ORIENTATION_PORTRAIT,
539                 wallpapers.get(0).getConfiguration().orientation);
540 
541         mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
542         // The transform state should keep because we expect to listen the signal from the
543         // transition executed by moving the task to front.
544         assertTrue(homeActivity.hasFixedRotationTransform());
545         assertTrue(mDefaultDisplay.isFixedRotationLaunchingApp(homeActivity));
546 
547         mDefaultDisplay.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(
548                 homeActivity.token);
549         // Wallpaper's transform state should be cleared with home.
550         assertFalse(homeActivity.hasFixedRotationTransform());
551         assertFalse(wallpaperWindowToken.hasFixedRotationTransform());
552     }
553 
554     @Test
testIsAnimatingByRecents()555     public void testIsAnimatingByRecents() {
556         final ActivityRecord homeActivity = createHomeActivity();
557         final Task rootTask = createTask(mDefaultDisplay);
558         final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */);
559         final Task leafTask = createTaskInRootTask(childTask, 0 /* userId */);
560         spyOn(leafTask);
561         doReturn(true).when(leafTask).isVisible();
562 
563         initializeRecentsAnimationController(mController, homeActivity);
564 
565         // Verify RecentsAnimationController will animate visible leaf task by default.
566         verify(mController).addAnimation(eq(leafTask), anyBoolean(), anyBoolean(), any());
567         assertTrue(leafTask.isAnimatingByRecents());
568 
569         // Make sure isAnimatingByRecents will also return true when it called by the parent task.
570         assertTrue(rootTask.isAnimatingByRecents());
571         assertTrue(childTask.isAnimatingByRecents());
572     }
573 
574     @Test
testRestoreNavBarWhenEnteringRecents_expectAnimation()575     public void testRestoreNavBarWhenEnteringRecents_expectAnimation() {
576         setupForShouldAttachNavBarDuringTransition();
577         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
578         final ActivityRecord homeActivity = createHomeActivity();
579         initializeRecentsAnimationController(mController, homeActivity);
580 
581         final WindowToken navToken = mDefaultDisplay.getDisplayPolicy().getNavigationBar().mToken;
582         final SurfaceControl.Transaction transaction = navToken.getPendingTransaction();
583 
584         verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
585                 eq(mDefaultDisplay.mDisplayId), eq(false));
586         verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl());
587         verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE);
588         assertTrue(mController.isNavigationBarAttachedToApp());
589 
590         mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
591         verify(mController).restoreNavigationBarFromApp(eq(true));
592         verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
593                 eq(mDefaultDisplay.mDisplayId), eq(true));
594         verify(transaction).setLayer(navToken.getSurfaceControl(), 0);
595         assertFalse(mController.isNavigationBarAttachedToApp());
596         assertTrue(navToken.isAnimating(ANIMATION_TYPE_TOKEN_TRANSFORM));
597     }
598 
599     @Test
testRestoreNavBarWhenBackToApp_expectNoAnimation()600     public void testRestoreNavBarWhenBackToApp_expectNoAnimation() {
601         setupForShouldAttachNavBarDuringTransition();
602         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
603         final ActivityRecord homeActivity = createHomeActivity();
604         initializeRecentsAnimationController(mController, homeActivity);
605 
606         final WindowToken navToken = mDefaultDisplay.getDisplayPolicy().getNavigationBar().mToken;
607         final SurfaceControl.Transaction transaction = navToken.getPendingTransaction();
608 
609         verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
610                 eq(mDefaultDisplay.mDisplayId), eq(false));
611         verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl());
612         verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE);
613         assertTrue(mController.isNavigationBarAttachedToApp());
614 
615         final WindowContainer parent = navToken.getParent();
616 
617         mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
618         verify(mController).restoreNavigationBarFromApp(eq(false));
619         verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
620                 eq(mDefaultDisplay.mDisplayId), eq(true));
621         verify(transaction).setLayer(navToken.getSurfaceControl(), 0);
622         verify(transaction).reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
623         assertFalse(mController.isNavigationBarAttachedToApp());
624         assertFalse(navToken.isAnimating(ANIMATION_TYPE_TOKEN_TRANSFORM));
625     }
626 
627     @Test
testAddTaskToTargets_expectAnimation()628     public void testAddTaskToTargets_expectAnimation() {
629         setupForShouldAttachNavBarDuringTransition();
630         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
631         final ActivityRecord homeActivity = createHomeActivity();
632         initializeRecentsAnimationController(mController, homeActivity);
633 
634         final WindowToken navToken = mDefaultDisplay.getDisplayPolicy().getNavigationBar().mToken;
635         final SurfaceControl.Transaction transaction = navToken.getPendingTransaction();
636 
637         verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
638                 eq(mDefaultDisplay.mDisplayId), eq(false));
639         verify(transaction).reparent(navToken.getSurfaceControl(), activity.getSurfaceControl());
640         verify(transaction).setLayer(navToken.getSurfaceControl(), Integer.MAX_VALUE);
641         assertTrue(mController.isNavigationBarAttachedToApp());
642 
643         final WindowContainer parent = navToken.getParent();
644 
645         mController.addTaskToTargets(createTask(mDefaultDisplay), (type, anim) -> {});
646         mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
647         verify(mController).restoreNavigationBarFromApp(eq(true));
648         verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
649                 eq(mDefaultDisplay.mDisplayId), eq(true));
650         verify(transaction).setLayer(navToken.getSurfaceControl(), 0);
651         assertFalse(mController.isNavigationBarAttachedToApp());
652         assertTrue(navToken.isAnimating(ANIMATION_TYPE_TOKEN_TRANSFORM));
653     }
654 
655     @Test
testNotAttachNavigationBar_controlledByFadeRotationAnimation()656     public void testNotAttachNavigationBar_controlledByFadeRotationAnimation() {
657         setupForShouldAttachNavBarDuringTransition();
658         AsyncRotationController mockController =
659                 mock(AsyncRotationController.class);
660         doReturn(mockController).when(mDefaultDisplay).getAsyncRotationController();
661         final ActivityRecord homeActivity = createHomeActivity();
662         initializeRecentsAnimationController(mController, homeActivity);
663         assertFalse(mController.isNavigationBarAttachedToApp());
664     }
665 
666     @Test
testAttachNavBarInSplitScreenMode()667     public void testAttachNavBarInSplitScreenMode() {
668         setupForShouldAttachNavBarDuringTransition();
669         TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm);
670         final ActivityRecord primary = createActivityRecordWithParentTask(
671                 organizer.createTaskToPrimary(true));
672         final ActivityRecord secondary = createActivityRecordWithParentTask(
673                 organizer.createTaskToSecondary(true));
674         final ActivityRecord homeActivity = createHomeActivity();
675         homeActivity.setVisibility(true);
676         initializeRecentsAnimationController(mController, homeActivity);
677 
678         WindowState navWindow = mController.getNavigationBarWindow();
679         final WindowToken navToken = navWindow.mToken;
680         final SurfaceControl.Transaction transaction = navToken.getPendingTransaction();
681 
682         verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
683                 eq(mDefaultDisplay.mDisplayId), eq(false));
684         verify(navWindow).setSurfaceTranslationY(-secondary.getBounds().top);
685         verify(transaction).reparent(navToken.getSurfaceControl(), secondary.getSurfaceControl());
686         assertTrue(mController.isNavigationBarAttachedToApp());
687         reset(navWindow);
688 
689         mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
690         final WindowContainer parent = navToken.getParent();
691         verify(mController.mStatusBar).setNavigationBarLumaSamplingEnabled(
692                 eq(mDefaultDisplay.mDisplayId), eq(true));
693         verify(navWindow).setSurfaceTranslationY(0);
694         verify(transaction).reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
695         verify(mController).restoreNavigationBarFromApp(eq(false));
696         assertFalse(mController.isNavigationBarAttachedToApp());
697     }
698 
699     @Test
testCleanupAnimation_expectExitAnimationDone()700     public void testCleanupAnimation_expectExitAnimationDone() {
701         mWm.setRecentsAnimationController(mController);
702         final ActivityRecord homeActivity = createHomeActivity();
703         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
704         final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
705         activity.addWindow(win1);
706 
707         initializeRecentsAnimationController(mController, homeActivity);
708         mController.startAnimation();
709 
710         spyOn(win1);
711         spyOn(win1.mWinAnimator);
712         // Simulate when the window is exiting and cleanupAnimation invoked
713         // (e.g. screen off during RecentsAnimation animating), will expect the window receives
714         // onExitAnimationDone to destroy the surface when the removal is allowed.
715         win1.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
716         win1.mHasSurface = true;
717         win1.mAnimatingExit = true;
718         win1.mRemoveOnExit = true;
719         win1.mWindowRemovalAllowed = true;
720         mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
721         verify(win1).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), any());
722         verify(win1).onExitAnimationDone();
723         verify(win1).destroySurface(eq(false), eq(false));
724         assertFalse(win1.mAnimatingExit);
725         assertFalse(win1.mHasSurface);
726     }
727 
728     @Test
testCancelForRotation_ReorderToTop()729     public void testCancelForRotation_ReorderToTop() throws Exception {
730         mWm.setRecentsAnimationController(mController);
731         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
732         final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
733         activity.addWindow(win1);
734 
735         mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */);
736         mController.setWillFinishToHome(true);
737         mController.cancelAnimationForDisplayChange();
738 
739         verify(mMockRunner).onAnimationCanceled(any(), any());
740         verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_TOP, false);
741     }
742 
743     @Test
testCancelForRotation_ReorderToOriginalPosition()744     public void testCancelForRotation_ReorderToOriginalPosition() throws Exception {
745         mWm.setRecentsAnimationController(mController);
746         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
747         final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
748         activity.addWindow(win1);
749 
750         mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */);
751         mController.setWillFinishToHome(false);
752         mController.cancelAnimationForDisplayChange();
753 
754         verify(mMockRunner).onAnimationCanceled(any(), any());
755         verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_ORIGINAL_POSITION, false);
756     }
757 
758     @Test
testCancelForStartHome()759     public void testCancelForStartHome() throws Exception {
760         mWm.setRecentsAnimationController(mController);
761         final ActivityRecord homeActivity = createHomeActivity();
762         final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
763         final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
764         activity.addWindow(win1);
765 
766         initializeRecentsAnimationController(mController, homeActivity);
767         mController.setWillFinishToHome(true);
768 
769         // Verify cancel is called with a snapshot and that we've created an overlay
770         spyOn(mWm.mTaskSnapshotController);
771         doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(),
772                 anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */);
773         mController.cancelAnimationForHomeStart();
774         verify(mMockRunner).onAnimationCanceled(any(), any());
775 
776         // Continue the animation (simulating a call to cleanupScreenshot())
777         mController.continueDeferredCancelAnimation();
778         verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_TOP, false);
779 
780         // Assume home was moved to front so will-be-top callback should not be called.
781         homeActivity.moveFocusableActivityToTop("test");
782         spyOn(mDefaultDisplay.mFixedRotationTransitionListener);
783         mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
784         verify(mDefaultDisplay.mFixedRotationTransitionListener, never()).notifyRecentsWillBeTop();
785     }
786 
createHomeActivity()787     private ActivityRecord createHomeActivity() {
788         final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
789                 .setParentTask(mRootHomeTask)
790                 .setCreateTask(true)
791                 .build();
792         // Avoid {@link RecentsAnimationController.TaskAnimationAdapter#createRemoteAnimationTarget}
793         // returning null when calling {@link RecentsAnimationController#createAppAnimations}.
794         homeActivity.setVisibility(true);
795         return homeActivity;
796     }
797 
startRecentsInDifferentRotation(ActivityRecord recentsActivity)798     private void startRecentsInDifferentRotation(ActivityRecord recentsActivity) {
799         final DisplayContent displayContent = recentsActivity.mDisplayContent;
800         displayContent.setFixedRotationLaunchingApp(recentsActivity,
801                 (displayContent.getRotation() + 1) % 4);
802         mController = new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
803                 displayContent.getDisplayId());
804         initializeRecentsAnimationController(mController, recentsActivity);
805         assertTrue(recentsActivity.hasFixedRotationTransform());
806     }
807 
assertTopFixedRotationLaunchingAppCleared(ActivityRecord activity)808     private static void assertTopFixedRotationLaunchingAppCleared(ActivityRecord activity) {
809         assertFalse(activity.hasFixedRotationTransform());
810         assertFalse(activity.mDisplayContent.hasTopFixedRotationLaunchingApp());
811     }
812 
setupForShouldAttachNavBarDuringTransition()813     private void setupForShouldAttachNavBarDuringTransition() {
814         final WindowState navBar = spy(createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar"));
815         mDefaultDisplay.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
816         mWm.setRecentsAnimationController(mController);
817         doReturn(navBar).when(mController).getNavigationBarWindow();
818         final DisplayPolicy displayPolicy = spy(mDefaultDisplay.getDisplayPolicy());
819         doReturn(displayPolicy).when(mDefaultDisplay).getDisplayPolicy();
820         doReturn(true).when(displayPolicy).shouldAttachNavBarToAppDuringTransition();
821     }
822 
initializeRecentsAnimationController(RecentsAnimationController controller, ActivityRecord activity)823     private static void initializeRecentsAnimationController(RecentsAnimationController controller,
824             ActivityRecord activity) {
825         controller.initialize(activity.getActivityType(), new SparseBooleanArray(), activity);
826     }
827 
verifyNoMoreInteractionsExceptAsBinder(IInterface binder)828     private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
829         verify(binder, atLeast(0)).asBinder();
830         verifyNoMoreInteractions(binder);
831     }
832 }
833