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.START_TASK_TO_FRONT;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
23 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
24 import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
25 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
26 import static android.view.WindowManager.TRANSIT_NONE;
27 
28 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
29 import static com.android.server.wm.BoundsAnimationController.BOUNDS;
30 import static com.android.server.wm.BoundsAnimationController.FADE_IN;
31 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
32 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
33 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
34 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS;
35 
36 import android.annotation.Nullable;
37 import android.app.ActivityOptions;
38 import android.content.ComponentName;
39 import android.content.Intent;
40 import android.os.RemoteException;
41 import android.os.Trace;
42 import android.util.Slog;
43 import android.view.IRecentsAnimationRunner;
44 
45 import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;
46 
47 /**
48  * Manages the recents animation, including the reordering of the stacks for the transition and
49  * cleanup. See {@link com.android.server.wm.RecentsAnimationController}.
50  */
51 class RecentsAnimation implements RecentsAnimationCallbacks,
52         ActivityDisplay.OnStackOrderChangedListener {
53     private static final String TAG = RecentsAnimation.class.getSimpleName();
54     private static final boolean DEBUG = DEBUG_RECENTS_ANIMATIONS;
55 
56     private final ActivityTaskManagerService mService;
57     private final ActivityStackSupervisor mStackSupervisor;
58     private final ActivityStartController mActivityStartController;
59     private final WindowManagerService mWindowManager;
60     private final ActivityDisplay mDefaultDisplay;
61     private final Intent mTargetIntent;
62     private final ComponentName mRecentsComponent;
63     private final int mRecentsUid;
64     private final @Nullable WindowProcessController mCaller;
65     private final int mUserId;
66     private final int mTargetActivityType;
67 
68     /**
69      * The activity which has been launched behind. We need to remember the activity because the
70      * target stack may have other activities, then we are able to restore the launch-behind state
71      * for the exact activity.
72      */
73     private ActivityRecord mLaunchedTargetActivity;
74 
75     // The stack to restore the target stack behind when the animation is finished
76     private ActivityStack mRestoreTargetBehindStack;
77 
RecentsAnimation(ActivityTaskManagerService atm, ActivityStackSupervisor stackSupervisor, ActivityStartController activityStartController, WindowManagerService wm, Intent targetIntent, ComponentName recentsComponent, int recentsUid, @Nullable WindowProcessController caller)78     RecentsAnimation(ActivityTaskManagerService atm, ActivityStackSupervisor stackSupervisor,
79             ActivityStartController activityStartController, WindowManagerService wm,
80             Intent targetIntent, ComponentName recentsComponent, int recentsUid,
81             @Nullable WindowProcessController caller) {
82         mService = atm;
83         mStackSupervisor = stackSupervisor;
84         mDefaultDisplay = mService.mRootActivityContainer.getDefaultDisplay();
85         mActivityStartController = activityStartController;
86         mWindowManager = wm;
87         mTargetIntent = targetIntent;
88         mRecentsComponent = recentsComponent;
89         mRecentsUid = recentsUid;
90         mCaller = caller;
91         mUserId = atm.getCurrentUserId();
92         mTargetActivityType = targetIntent.getComponent() != null
93                 && recentsComponent.equals(targetIntent.getComponent())
94                         ? ACTIVITY_TYPE_RECENTS
95                         : ACTIVITY_TYPE_HOME;
96     }
97 
98     /**
99      * Starts the recents activity in background without animation if the record doesn't exist or
100      * the client isn't launched. If the recents activity is already alive, ensure its configuration
101      * is updated to the current one.
102      */
preloadRecentsActivity()103     void preloadRecentsActivity() {
104         if (DEBUG) Slog.d(TAG, "Preload recents with " + mTargetIntent);
105         ActivityStack targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED,
106                 mTargetActivityType);
107         ActivityRecord targetActivity = getTargetActivity(targetStack);
108         if (targetActivity != null) {
109             if (targetActivity.visible || targetActivity.isTopRunningActivity()) {
110                 // The activity is ready.
111                 return;
112             }
113             if (targetActivity.attachedToProcess()) {
114                 // The activity may be relaunched if it cannot handle the current configuration
115                 // changes. The activity will be paused state if it is relaunched, otherwise it
116                 // keeps the original stopped state.
117                 targetActivity.ensureActivityConfiguration(0 /* globalChanges */,
118                         false /* preserveWindow */, true /* ignoreVisibility */);
119                 if (DEBUG) Slog.d(TAG, "Updated config=" + targetActivity.getConfiguration());
120             }
121         } else {
122             // Create the activity record. Because the activity is invisible, this doesn't really
123             // start the client.
124             startRecentsActivityInBackground("preloadRecents");
125             targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType);
126             targetActivity = getTargetActivity(targetStack);
127             if (targetActivity == null) {
128                 Slog.w(TAG, "Cannot start " + mTargetIntent);
129                 return;
130             }
131         }
132 
133         if (!targetActivity.attachedToProcess()) {
134             if (DEBUG) Slog.d(TAG, "Real start recents");
135             mStackSupervisor.startSpecificActivityLocked(targetActivity, false /* andResume */,
136                     false /* checkConfig */);
137             // Make sure the activity won't be involved in transition.
138             if (targetActivity.mAppWindowToken != null) {
139                 targetActivity.mAppWindowToken.getDisplayContent().mUnknownAppVisibilityController
140                         .appRemovedOrHidden(targetActivity.mAppWindowToken);
141             }
142         }
143 
144         // Invisible activity should be stopped. If the recents activity is alive and its doesn't
145         // need to relaunch by current configuration, then it may be already in stopped state.
146         if (!targetActivity.isState(ActivityStack.ActivityState.STOPPING,
147                 ActivityStack.ActivityState.STOPPED)) {
148             // Add to stopping instead of stop immediately. So the client has the chance to perform
149             // traversal in non-stopped state (ViewRootImpl.mStopped) that would initialize more
150             // things (e.g. the measure can be done earlier). The actual stop will be performed when
151             // it reports idle.
152             targetStack.addToStopping(targetActivity, true /* scheduleIdle */,
153                     true /* idleDelayed */, "preloadRecents");
154         }
155     }
156 
startRecentsActivity(IRecentsAnimationRunner recentsAnimationRunner)157     void startRecentsActivity(IRecentsAnimationRunner recentsAnimationRunner) {
158         if (DEBUG) Slog.d(TAG, "startRecentsActivity(): intent=" + mTargetIntent);
159         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "RecentsAnimation#startRecentsActivity");
160 
161         // TODO(multi-display) currently only support recents animation in default display.
162         final DisplayContent dc =
163                 mService.mRootActivityContainer.getDefaultDisplay().mDisplayContent;
164         if (!mWindowManager.canStartRecentsAnimation()) {
165             notifyAnimationCancelBeforeStart(recentsAnimationRunner);
166             if (DEBUG) Slog.d(TAG, "Can't start recents animation, nextAppTransition="
167                         + dc.mAppTransition.getAppTransition());
168             return;
169         }
170 
171         // If the activity is associated with the recents stack, then try and get that first
172         ActivityStack targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED,
173                 mTargetActivityType);
174         ActivityRecord targetActivity = getTargetActivity(targetStack);
175         final boolean hasExistingActivity = targetActivity != null;
176         if (hasExistingActivity) {
177             final ActivityDisplay display = targetActivity.getDisplay();
178             mRestoreTargetBehindStack = display.getStackAbove(targetStack);
179             if (mRestoreTargetBehindStack == null) {
180                 notifyAnimationCancelBeforeStart(recentsAnimationRunner);
181                 if (DEBUG) Slog.d(TAG, "No stack above target stack=" + targetStack);
182                 return;
183             }
184         }
185 
186         // Send launch hint if we are actually launching the target. If it's already visible
187         // (shouldn't happen in general) we don't need to send it.
188         if (targetActivity == null || !targetActivity.visible) {
189             mService.mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded(
190                     true /* forceSend */, targetActivity);
191         }
192 
193         mStackSupervisor.getActivityMetricsLogger().notifyActivityLaunching(mTargetIntent);
194 
195         if (mCaller != null) {
196             mCaller.setRunningRecentsAnimation(true);
197         }
198 
199         mWindowManager.deferSurfaceLayout();
200         try {
201             if (hasExistingActivity) {
202                 // Move the recents activity into place for the animation if it is not top most
203                 mDefaultDisplay.moveStackBehindBottomMostVisibleStack(targetStack);
204                 if (DEBUG) Slog.d(TAG, "Moved stack=" + targetStack + " behind stack="
205                             + mDefaultDisplay.getStackAbove(targetStack));
206 
207                 // If there are multiple tasks in the target stack (ie. the home stack, with 3p
208                 // and default launchers coexisting), then move the task to the top as a part of
209                 // moving the stack to the front
210                 if (targetStack.topTask() != targetActivity.getTaskRecord()) {
211                     targetStack.addTask(targetActivity.getTaskRecord(), true /* toTop */,
212                             "startRecentsActivity");
213                 }
214             } else {
215                 // No recents activity, create the new recents activity bottom most
216                 startRecentsActivityInBackground("startRecentsActivity_noTargetActivity");
217 
218                 // Move the recents activity into place for the animation
219                 targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED,
220                         mTargetActivityType);
221                 targetActivity = getTargetActivity(targetStack);
222                 mDefaultDisplay.moveStackBehindBottomMostVisibleStack(targetStack);
223                 if (DEBUG) {
224                     Slog.d(TAG, "Moved stack=" + targetStack + " behind stack="
225                             + mDefaultDisplay.getStackAbove(targetStack));
226                 }
227 
228                 mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
229                 mWindowManager.executeAppTransition();
230 
231                 // TODO: Maybe wait for app to draw in this particular case?
232 
233                 if (DEBUG) Slog.d(TAG, "Started intent=" + mTargetIntent);
234             }
235 
236             // Mark the target activity as launch-behind to bump its visibility for the
237             // duration of the gesture that is driven by the recents component
238             targetActivity.mLaunchTaskBehind = true;
239             mLaunchedTargetActivity = targetActivity;
240 
241             // Fetch all the surface controls and pass them to the client to get the animation
242             // started. Cancel any existing recents animation running synchronously (do not hold the
243             // WM lock)
244             mWindowManager.cancelRecentsAnimationSynchronously(REORDER_MOVE_TO_ORIGINAL_POSITION,
245                     "startRecentsActivity");
246             mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
247                     this, mDefaultDisplay.mDisplayId,
248                     mStackSupervisor.mRecentTasks.getRecentTaskIds());
249 
250             // If we updated the launch-behind state, update the visibility of the activities after
251             // we fetch the visible tasks to be controlled by the animation
252             mService.mRootActivityContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
253 
254             mStackSupervisor.getActivityMetricsLogger().notifyActivityLaunched(START_TASK_TO_FRONT,
255                     targetActivity);
256 
257             // Register for stack order changes
258             mDefaultDisplay.registerStackOrderChangedListener(this);
259         } catch (Exception e) {
260             Slog.e(TAG, "Failed to start recents activity", e);
261             throw e;
262         } finally {
263             mWindowManager.continueSurfaceLayout();
264             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
265         }
266     }
267 
finishAnimation(@ecentsAnimationController.ReorderMode int reorderMode, boolean sendUserLeaveHint)268     private void finishAnimation(@RecentsAnimationController.ReorderMode int reorderMode,
269             boolean sendUserLeaveHint) {
270         synchronized (mService.mGlobalLock) {
271             if (DEBUG) Slog.d(TAG, "onAnimationFinished(): controller="
272                     + mWindowManager.getRecentsAnimationController()
273                     + " reorderMode=" + reorderMode);
274 
275             // Unregister for stack order changes
276             mDefaultDisplay.unregisterStackOrderChangedListener(this);
277 
278             final RecentsAnimationController controller =
279                     mWindowManager.getRecentsAnimationController();
280             if (controller == null) return;
281 
282             // Just to be sure end the launch hint in case the target activity was never launched.
283             // However, if we're keeping the activity and making it visible, we can leave it on.
284             if (reorderMode != REORDER_KEEP_IN_PLACE) {
285                 mService.mRootActivityContainer.sendPowerHintForLaunchEndIfNeeded();
286             }
287 
288             // Once the target is shown, prevent spurious background app switches
289             if (reorderMode == REORDER_MOVE_TO_TOP) {
290                 mService.stopAppSwitches();
291             }
292 
293             if (mCaller != null) {
294                 mCaller.setRunningRecentsAnimation(false);
295             }
296 
297             mWindowManager.inSurfaceTransaction(() -> {
298                 Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
299                         "RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
300                 mWindowManager.deferSurfaceLayout();
301                 try {
302                     mWindowManager.cleanupRecentsAnimation(reorderMode);
303 
304                     final ActivityStack targetStack = mDefaultDisplay.getStack(
305                             WINDOWING_MODE_UNDEFINED, mTargetActivityType);
306                     // Prefer to use the original target activity instead of top activity because
307                     // we may have moved another task to top (starting 3p launcher).
308                     final ActivityRecord targetActivity = targetStack != null
309                             ? targetStack.isInStackLocked(mLaunchedTargetActivity)
310                             : null;
311                     if (DEBUG) Slog.d(TAG, "onAnimationFinished(): targetStack=" + targetStack
312                             + " targetActivity=" + targetActivity
313                             + " mRestoreTargetBehindStack=" + mRestoreTargetBehindStack);
314                     if (targetActivity == null) {
315                         return;
316                     }
317 
318                     // Restore the launched-behind state
319                     targetActivity.mLaunchTaskBehind = false;
320 
321                     if (reorderMode == REORDER_MOVE_TO_TOP) {
322                         // Bring the target stack to the front
323                         mStackSupervisor.mNoAnimActivities.add(targetActivity);
324 
325                         if (sendUserLeaveHint) {
326                             // Setting this allows the previous app to PiP.
327                             mStackSupervisor.mUserLeaving = true;
328                             targetStack.moveTaskToFrontLocked(targetActivity.getTaskRecord(),
329                                     true /* noAnimation */, null /* activityOptions */,
330                                     targetActivity.appTimeTracker,
331                                     "RecentsAnimation.onAnimationFinished()");
332                         } else {
333                             targetStack.moveToFront("RecentsAnimation.onAnimationFinished()");
334                         }
335 
336                         if (DEBUG) {
337                             final ActivityStack topStack = getTopNonAlwaysOnTopStack();
338                             if (topStack != targetStack) {
339                                 Slog.w(TAG, "Expected target stack=" + targetStack
340                                         + " to be top most but found stack=" + topStack);
341                             }
342                         }
343                     } else if (reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION){
344                         // Restore the target stack to its previous position
345                         final ActivityDisplay display = targetActivity.getDisplay();
346                         display.moveStackBehindStack(targetStack, mRestoreTargetBehindStack);
347                         if (DEBUG) {
348                             final ActivityStack aboveTargetStack =
349                                     mDefaultDisplay.getStackAbove(targetStack);
350                             if (mRestoreTargetBehindStack != null
351                                     && aboveTargetStack != mRestoreTargetBehindStack) {
352                                 Slog.w(TAG, "Expected target stack=" + targetStack
353                                         + " to restored behind stack=" + mRestoreTargetBehindStack
354                                         + " but it is behind stack=" + aboveTargetStack);
355                             }
356                         }
357                     } else {
358                         // If there is no recents screenshot animation, we can update the visibility
359                         // of target stack immediately because it is visually invisible and the
360                         // launch-behind state is restored. That also prevents the next transition
361                         // type being disturbed if the visibility is updated after setting the next
362                         // transition (the target activity will be one of closing apps).
363                         if (!controller.shouldDeferCancelWithScreenshot()
364                                 && !targetStack.isFocusedStackOnDisplay()) {
365                             targetStack.ensureActivitiesVisibleLocked(null /* starting */,
366                                     0 /* starting */, false /* preserveWindows */);
367                         }
368                         // Keep target stack in place, nothing changes, so ignore the transition
369                         // logic below
370                         return;
371                     }
372 
373                     mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
374                     mService.mRootActivityContainer.ensureActivitiesVisible(null, 0, false);
375                     mService.mRootActivityContainer.resumeFocusedStacksTopActivities();
376 
377                     // No reason to wait for the pausing activity in this case, as the hiding of
378                     // surfaces needs to be done immediately.
379                     mWindowManager.executeAppTransition();
380 
381                     // After reordering the stacks, reset the minimized state. At this point, either
382                     // the target activity is now top-most and we will stay minimized (if in
383                     // split-screen), or we will have returned to the app, and the minimized state
384                     // should be reset
385                     mWindowManager.checkSplitScreenMinimizedChanged(true /* animate */);
386                 } catch (Exception e) {
387                     Slog.e(TAG, "Failed to clean up recents activity", e);
388                     throw e;
389                 } finally {
390                     mWindowManager.continueSurfaceLayout();
391                     Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
392                 }
393             });
394         }
395     }
396 
397     @Override
onAnimationFinished(@ecentsAnimationController.ReorderMode int reorderMode, boolean runSychronously, boolean sendUserLeaveHint)398     public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode,
399             boolean runSychronously, boolean sendUserLeaveHint) {
400         if (runSychronously) {
401             finishAnimation(reorderMode, sendUserLeaveHint);
402         } else {
403             mService.mH.post(() -> finishAnimation(reorderMode, sendUserLeaveHint));
404         }
405     }
406 
407     @Override
onStackOrderChanged(ActivityStack stack)408     public void onStackOrderChanged(ActivityStack stack) {
409         if (DEBUG) Slog.d(TAG, "onStackOrderChanged(): stack=" + stack);
410         if (mDefaultDisplay.getIndexOf(stack) == -1 || !stack.shouldBeVisible(null)) {
411             // The stack is not visible, so ignore this change
412             return;
413         }
414         final RecentsAnimationController controller =
415                 mWindowManager.getRecentsAnimationController();
416         if (controller == null) {
417             return;
418         }
419 
420         final DisplayContent dc =
421                 mService.mRootActivityContainer.getDefaultDisplay().mDisplayContent;
422         dc.mBoundsAnimationController.setAnimationType(
423                 controller.shouldDeferCancelUntilNextTransition() ? FADE_IN : BOUNDS);
424 
425         // We defer canceling the recents animation until the next app transition in the following
426         // cases:
427         // 1) The next launching task is not being animated by the recents animation
428         // 2) The next task is home activity. (i.e. pressing home key to back home in recents).
429         if ((!controller.isAnimatingTask(stack.getTaskStack().getTopChild())
430                 || controller.isTargetApp(stack.getTopActivity().mAppWindowToken))
431                 && controller.shouldDeferCancelUntilNextTransition()) {
432             // Always prepare an app transition since we rely on the transition callbacks to cleanup
433             mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
434             controller.setCancelOnNextTransitionStart();
435         } else {
436             // Just cancel directly to unleash from launcher when the next launching task is the
437             // current top task.
438             mWindowManager.cancelRecentsAnimationSynchronously(REORDER_KEEP_IN_PLACE,
439                     "stackOrderChanged");
440         }
441     }
442 
startRecentsActivityInBackground(String reason)443     private void startRecentsActivityInBackground(String reason) {
444         final ActivityOptions options = ActivityOptions.makeBasic();
445         options.setLaunchActivityType(mTargetActivityType);
446         options.setAvoidMoveToFront();
447         mTargetIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION);
448 
449         mActivityStartController
450                 .obtainStarter(mTargetIntent, reason)
451                 .setCallingUid(mRecentsUid)
452                 .setCallingPackage(mRecentsComponent.getPackageName())
453                 .setActivityOptions(new SafeActivityOptions(options))
454                 .setMayWait(mUserId)
455                 .execute();
456     }
457 
458     /**
459      * Called only when the animation should be canceled prior to starting.
460      */
notifyAnimationCancelBeforeStart(IRecentsAnimationRunner recentsAnimationRunner)461     static void notifyAnimationCancelBeforeStart(IRecentsAnimationRunner recentsAnimationRunner) {
462         try {
463             recentsAnimationRunner.onAnimationCanceled(false /* deferredWithScreenshot */);
464         } catch (RemoteException e) {
465             Slog.e(TAG, "Failed to cancel recents animation before start", e);
466         }
467     }
468 
469     /**
470      * @return The top stack that is not always-on-top.
471      */
getTopNonAlwaysOnTopStack()472     private ActivityStack getTopNonAlwaysOnTopStack() {
473         for (int i = mDefaultDisplay.getChildCount() - 1; i >= 0; i--) {
474             final ActivityStack s = mDefaultDisplay.getChildAt(i);
475             if (s.getWindowConfiguration().isAlwaysOnTop()) {
476                 continue;
477             }
478             return s;
479         }
480         return null;
481     }
482 
483     /**
484      * @return the top activity in the {@param targetStack} matching the {@param component}, or just
485      * the top activity of the top task if no task matches the component.
486      */
getTargetActivity(ActivityStack targetStack)487     private ActivityRecord getTargetActivity(ActivityStack targetStack) {
488         if (targetStack == null) {
489             return null;
490         }
491 
492         for (int i = targetStack.getChildCount() - 1; i >= 0; i--) {
493             final TaskRecord task = targetStack.getChildAt(i);
494             if (task.userId == mUserId
495                     && task.getBaseIntent().getComponent().equals(mTargetIntent.getComponent())) {
496                 return task.getTopActivity();
497             }
498         }
499         return null;
500     }
501 }
502