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.app.ActivityManager.PROCESS_STATE_HOME;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
23 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
24 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
25 
26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
27 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
29 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
30 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
32 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
33 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
34 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
35 import static com.android.server.wm.ActivityRecord.State.PAUSED;
36 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
37 import static com.android.server.wm.WindowContainer.POSITION_TOP;
38 
39 import static com.google.common.truth.Truth.assertThat;
40 
41 import static org.junit.Assert.assertEquals;
42 import static org.junit.Assert.assertFalse;
43 import static org.junit.Assert.assertTrue;
44 import static org.mockito.ArgumentMatchers.any;
45 import static org.mockito.ArgumentMatchers.anyBoolean;
46 import static org.mockito.ArgumentMatchers.anyInt;
47 
48 import android.content.ComponentName;
49 import android.content.Intent;
50 import android.content.pm.ActivityInfo;
51 import android.content.pm.ApplicationInfo;
52 import android.platform.test.annotations.Presubmit;
53 import android.view.IRecentsAnimationRunner;
54 
55 import androidx.test.filters.MediumTest;
56 
57 import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;
58 
59 import org.junit.Before;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 
63 /**
64  * Build/Install/Run:
65  *  atest WmTests:RecentsAnimationTest
66  */
67 @MediumTest
68 @Presubmit
69 @RunWith(WindowTestRunner.class)
70 public class RecentsAnimationTest extends WindowTestsBase {
71 
72     private static final int TEST_USER_ID = 100;
73 
74     private final ComponentName mRecentsComponent =
75             new ComponentName(mContext.getPackageName(), "RecentsActivity");
76     private RecentsAnimationController mRecentsAnimationController;
77 
78     @Before
setUp()79     public void setUp() throws Exception {
80         mRecentsAnimationController = mock(RecentsAnimationController.class);
81         mAtm.mWindowManager.setRecentsAnimationController(mRecentsAnimationController);
82         doNothing().when(mAtm.mWindowManager).initializeRecentsAnimation(
83                 anyInt(), any(), any(), anyInt(), any(), any());
84 
85         final RecentTasks recentTasks = mAtm.getRecentTasks();
86         spyOn(recentTasks);
87         doReturn(mRecentsComponent).when(recentTasks).getRecentsComponent();
88     }
89 
90     @Test
testRecentsActivityVisiblility()91     public void testRecentsActivityVisiblility() {
92         TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
93         Task recentsStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
94                 ACTIVITY_TYPE_RECENTS, true /* onTop */);
95         final WindowProcessController wpc = mSystemServicesTestRule.addProcess(
96                 mRecentsComponent.getPackageName(), mRecentsComponent.getPackageName(),
97                 // Use real pid/uid of the test so the corresponding process can be mapped by
98                 // Binder.getCallingPid/Uid.
99                 WindowManagerService.MY_PID, WindowManagerService.MY_UID);
100         ActivityRecord recentActivity = new ActivityBuilder(mAtm)
101                 .setComponent(mRecentsComponent)
102                 .setTask(recentsStack)
103                 .setUseProcess(wpc)
104                 .build();
105         ActivityRecord topActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
106         topActivity.getRootTask().moveToFront("testRecentsActivityVisiblility");
107 
108         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
109                 any() /* starting */, anyInt() /* configChanges */,
110                 anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
111 
112         RecentsAnimationCallbacks recentsAnimation = startRecentsActivity(
113                 mRecentsComponent, true /* getRecentsAnimation */);
114         // The launch-behind state should make the recents activity visible.
115         assertTrue(recentActivity.isVisibleRequested());
116         assertEquals(ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS,
117                 mAtm.mDemoteTopAppReasons);
118         assertFalse(mAtm.mInternal.useTopSchedGroupForTopProcess());
119 
120         // Simulate the animation is cancelled without changing the stack order.
121         recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */);
122         // The non-top recents activity should be invisible by the restored launch-behind state.
123         assertFalse(recentActivity.isVisibleRequested());
124         assertEquals(0, mAtm.mDemoteTopAppReasons);
125     }
126 
127     @Test
testPreloadRecentsActivity()128     public void testPreloadRecentsActivity() {
129         TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
130         final Task homeStack =
131                 defaultTaskDisplayArea.getRootTask(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
132         defaultTaskDisplayArea.positionChildAt(POSITION_TOP, homeStack,
133                 false /* includingParents */);
134         ActivityRecord topRunningHomeActivity = homeStack.topRunningActivity();
135         if (topRunningHomeActivity == null) {
136             topRunningHomeActivity = new ActivityBuilder(mAtm)
137                     .setParentTask(homeStack)
138                     .setCreateTask(true)
139                     .build();
140         }
141 
142         ActivityInfo aInfo = new ActivityInfo();
143         aInfo.applicationInfo = new ApplicationInfo();
144         aInfo.applicationInfo.uid = 10001;
145         aInfo.applicationInfo.targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
146         aInfo.packageName = aInfo.applicationInfo.packageName = mRecentsComponent.getPackageName();
147         aInfo.processName = "recents";
148         doReturn(aInfo).when(mSupervisor).resolveActivity(any() /* intent */, any() /* rInfo */,
149                 anyInt() /* startFlags */, any() /* profilerInfo */);
150 
151         // Assume its process is alive because the caller should be the recents service.
152         final WindowProcessController proc = mSystemServicesTestRule.addProcess(aInfo.packageName,
153                 aInfo.processName, 12345 /* pid */, aInfo.applicationInfo.uid);
154         proc.setCurrentProcState(PROCESS_STATE_HOME);
155 
156         Intent recentsIntent = new Intent().setComponent(mRecentsComponent);
157         // Null animation indicates to preload.
158         mAtm.startRecentsActivity(recentsIntent, 0 /* eventTime */,
159                 null /* recentsAnimationRunner */);
160 
161         Task recentsStack = defaultTaskDisplayArea.getRootTask(WINDOWING_MODE_FULLSCREEN,
162                 ACTIVITY_TYPE_RECENTS);
163         assertThat(recentsStack).isNotNull();
164 
165         ActivityRecord recentsActivity = recentsStack.getTopNonFinishingActivity();
166         // The activity is started in background so it should be invisible and will be stopped.
167         assertThat(recentsActivity).isNotNull();
168         assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
169         assertFalse(recentsActivity.isVisibleRequested());
170 
171         // Assume it is stopped to test next use case.
172         recentsActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */,
173                 null /* description */);
174         mSupervisor.mStoppingActivities.remove(recentsActivity);
175 
176         spyOn(recentsActivity);
177         // Start when the recents activity exists. It should ensure the configuration.
178         mAtm.startRecentsActivity(recentsIntent, 0 /* eventTime */,
179                 null /* recentsAnimationRunner */);
180 
181         verify(recentsActivity).ensureActivityConfiguration(anyInt() /* globalChanges */,
182                 anyBoolean() /* preserveWindow */, eq(true) /* ignoreVisibility */);
183         assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
184     }
185 
186     @Test
testRestartRecentsActivity()187     public void testRestartRecentsActivity() throws Exception {
188         // Have a recents activity that is not attached to its process (ActivityRecord.app = null).
189         TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
190         Task recentsStack = defaultTaskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
191                 ACTIVITY_TYPE_RECENTS, true /* onTop */);
192         ActivityRecord recentActivity = new ActivityBuilder(mAtm).setComponent(
193                 mRecentsComponent).setCreateTask(true).setParentTask(recentsStack).build();
194         WindowProcessController app = recentActivity.app;
195         recentActivity.app = null;
196 
197         // Start an activity on top.
198         new ActivityBuilder(mAtm).setCreateTask(true).build().getRootTask().moveToFront(
199                 "testRestartRecentsActivity");
200 
201         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
202                 any() /* starting */, anyInt() /* configChanges */,
203                 anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
204         doReturn(app).when(mAtm).getProcessController(eq(recentActivity.processName), anyInt());
205         ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager();
206         doNothing().when(lifecycleManager).scheduleTransaction(any());
207 
208         startRecentsActivity();
209 
210         // Recents activity must be restarted, but not be resumed while running recents animation.
211         verify(mRootWindowContainer.mTaskSupervisor).startSpecificActivity(
212                 eq(recentActivity), eq(false), anyBoolean());
213         assertThat(recentActivity.getState()).isEqualTo(PAUSED);
214     }
215 
216     @Test
testSetLaunchTaskBehindOfTargetActivity()217     public void testSetLaunchTaskBehindOfTargetActivity() {
218         TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
219         Task homeStack = taskDisplayArea.getRootHomeTask();
220         // Assume the home activity support recents.
221         ActivityRecord targetActivity = homeStack.getTopNonFinishingActivity();
222         if (targetActivity == null) {
223             targetActivity = new ActivityBuilder(mAtm)
224                     .setCreateTask(true)
225                     .setParentTask(homeStack)
226                     .build();
227         }
228 
229         // Put another home activity in home stack.
230         ActivityRecord anotherHomeActivity = new ActivityBuilder(mAtm)
231                 .setComponent(new ComponentName(mContext.getPackageName(), "Home2"))
232                 .setCreateTask(true)
233                 .setParentTask(homeStack)
234                 .build();
235         // Start an activity on top so the recents activity can be started.
236         new ActivityBuilder(mAtm)
237                 .setCreateTask(true)
238                 .build()
239                 .getRootTask()
240                 .moveToFront("Activity start");
241 
242         // Start the recents animation.
243         RecentsAnimationCallbacks recentsAnimation = startRecentsActivity(
244                 targetActivity.getTask().getBaseIntent().getComponent(),
245                 true /* getRecentsAnimation */);
246         // Ensure launch-behind is set for being visible.
247         assertTrue(targetActivity.mLaunchTaskBehind);
248 
249         anotherHomeActivity.moveFocusableActivityToTop("launchAnotherHome");
250 
251         // The test uses mocked RecentsAnimationController so we have to invoke the callback
252         // manually to simulate the flow.
253         recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */);
254         // We should restore the launch-behind of the original target activity.
255         assertFalse(targetActivity.mLaunchTaskBehind);
256     }
257 
258     @Test
testCancelAnimationOnVisibleStackOrderChange()259     public void testCancelAnimationOnVisibleStackOrderChange() {
260         TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
261         Task fullscreenStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
262                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
263         new ActivityBuilder(mAtm)
264                 .setComponent(new ComponentName(mContext.getPackageName(), "App1"))
265                 .setCreateTask(true)
266                 .setParentTask(fullscreenStack)
267                 .build();
268         Task recentsStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
269                 ACTIVITY_TYPE_RECENTS, true /* onTop */);
270         new ActivityBuilder(mAtm)
271                 .setComponent(mRecentsComponent)
272                 .setCreateTask(true)
273                 .setParentTask(recentsStack)
274                 .build();
275         Task fullscreenStack2 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
276                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
277         new ActivityBuilder(mAtm)
278                 .setComponent(new ComponentName(mContext.getPackageName(), "App2"))
279                 .setCreateTask(true)
280                 .setParentTask(fullscreenStack2)
281                 .build();
282 
283         // Start the recents animation
284         startRecentsActivity();
285 
286         fullscreenStack.moveToFront("Activity start");
287 
288         // Assume recents animation already started, set a state that cancel recents animation
289         // with screenshot.
290         doReturn(true).when(mRecentsAnimationController).shouldDeferCancelUntilNextTransition();
291         doReturn(true).when(mRecentsAnimationController).shouldDeferCancelWithScreenshot();
292         // Start another fullscreen activity.
293         fullscreenStack2.moveToFront("Activity start");
294 
295         // Ensure that the recents animation was canceled by setCancelOnNextTransitionStart().
296         verify(mRecentsAnimationController, times(1)).setCancelOnNextTransitionStart();
297     }
298 
299     @Test
testKeepAnimationOnHiddenStackOrderChange()300     public void testKeepAnimationOnHiddenStackOrderChange() {
301         TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
302         Task fullscreenStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
303                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
304         new ActivityBuilder(mAtm)
305                 .setComponent(new ComponentName(mContext.getPackageName(), "App1"))
306                 .setCreateTask(true)
307                 .setParentTask(fullscreenStack)
308                 .build();
309         Task recentsStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
310                 ACTIVITY_TYPE_RECENTS, true /* onTop */);
311         new ActivityBuilder(mAtm)
312                 .setComponent(mRecentsComponent)
313                 .setCreateTask(true)
314                 .setParentTask(recentsStack)
315                 .build();
316         Task fullscreenStack2 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
317                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
318         new ActivityBuilder(mAtm)
319                 .setComponent(new ComponentName(mContext.getPackageName(), "App2"))
320                 .setCreateTask(true)
321                 .setParentTask(fullscreenStack2)
322                 .build();
323 
324         // Start the recents animation
325         startRecentsActivity();
326 
327         fullscreenStack.removeIfPossible();
328 
329         // Ensure that the recents animation was NOT canceled
330         verify(mAtm.mWindowManager, times(0)).cancelRecentsAnimation(
331                 eq(REORDER_KEEP_IN_PLACE), any());
332         verify(mRecentsAnimationController, times(0)).setCancelOnNextTransitionStart();
333     }
334 
335     @Test
testMultipleUserHomeActivity_findUserHomeTask()336     public void testMultipleUserHomeActivity_findUserHomeTask() {
337         TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultDisplay()
338                 .getDefaultTaskDisplayArea();
339         Task homeStack = taskDisplayArea.getRootTask(WINDOWING_MODE_UNDEFINED,
340                 ACTIVITY_TYPE_HOME);
341         ActivityRecord otherUserHomeActivity = new ActivityBuilder(mAtm)
342                 .setParentTask(homeStack)
343                 .setCreateTask(true)
344                 .setComponent(new ComponentName(mContext.getPackageName(), "Home2"))
345                 .build();
346         otherUserHomeActivity.getTask().mUserId = TEST_USER_ID;
347 
348         Task fullscreenStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
349                 ACTIVITY_TYPE_STANDARD, true /* onTop */);
350         new ActivityBuilder(mAtm)
351                 .setComponent(new ComponentName(mContext.getPackageName(), "App1"))
352                 .setCreateTask(true)
353                 .setParentTask(fullscreenStack)
354                 .build();
355 
356         doReturn(TEST_USER_ID).when(mAtm).getCurrentUserId();
357         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
358                 any() /* starting */, anyInt() /* configChanges */,
359                 anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
360 
361         startRecentsActivity(otherUserHomeActivity.getTask().getBaseIntent().getComponent(),
362                 true);
363 
364         // Ensure we find the task for the right user and it is made visible
365         assertTrue(otherUserHomeActivity.isVisibleRequested());
366     }
367 
startRecentsActivity()368     private void startRecentsActivity() {
369         startRecentsActivity(mRecentsComponent, false /* getRecentsAnimation */);
370     }
371 
372     /**
373      * @return non-null {@link RecentsAnimationCallbacks} if the given {@code getRecentsAnimation}
374      *         is {@code true}.
375      */
startRecentsActivity(ComponentName recentsComponent, boolean getRecentsAnimation)376     private RecentsAnimationCallbacks startRecentsActivity(ComponentName recentsComponent,
377             boolean getRecentsAnimation) {
378         RecentsAnimationCallbacks[] recentsAnimation = { null };
379         if (getRecentsAnimation) {
380             doAnswer(invocation -> {
381                 // The callback is actually RecentsAnimation.
382                 recentsAnimation[0] = invocation.getArgument(2);
383                 return null;
384             }).when(mAtm.mWindowManager).initializeRecentsAnimation(
385                     anyInt() /* targetActivityType */, any() /* recentsAnimationRunner */,
386                     any() /* callbacks */, anyInt() /* displayId */, any() /* recentTaskIds */,
387                     any() /* targetActivity */);
388         }
389 
390         Intent recentsIntent = new Intent();
391         recentsIntent.setComponent(recentsComponent);
392         mAtm.startRecentsActivity(recentsIntent, 0 /* eventTime */,
393                 mock(IRecentsAnimationRunner.class));
394         return recentsAnimation[0];
395     }
396 }
397