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
17package com.android.server.am;
18
19import static android.app.ActivityManager.START_TASK_TO_FRONT;
20import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
21import static android.app.AppOpsManager.OP_NONE;
22import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
23import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
24import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
25import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
26import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
27import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
28import static android.view.WindowManager.TRANSIT_NONE;
29import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
30import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
31import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
32import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
33
34import android.app.ActivityOptions;
35import android.app.AppOpsManager;
36import android.app.IAssistDataReceiver;
37import android.content.ComponentName;
38import android.content.Context;
39import android.content.Intent;
40import android.os.RemoteException;
41import android.os.Trace;
42import android.util.Slog;
43import android.view.IRecentsAnimationRunner;
44import com.android.server.wm.RecentsAnimationController;
45import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;
46import com.android.server.wm.WindowManagerService;
47
48/**
49 * Manages the recents animation, including the reordering of the stacks for the transition and
50 * cleanup. See {@link com.android.server.wm.RecentsAnimationController}.
51 */
52class RecentsAnimation implements RecentsAnimationCallbacks,
53        ActivityDisplay.OnStackOrderChangedListener {
54    private static final String TAG = RecentsAnimation.class.getSimpleName();
55    private static final boolean DEBUG = false;
56
57    private final ActivityManagerService mService;
58    private final ActivityStackSupervisor mStackSupervisor;
59    private final ActivityStartController mActivityStartController;
60    private final WindowManagerService mWindowManager;
61    private final UserController mUserController;
62    private final ActivityDisplay mDefaultDisplay;
63    private final int mCallingPid;
64
65    private int mTargetActivityType;
66    private AssistDataRequester mAssistDataRequester;
67
68    // The stack to restore the target stack behind when the animation is finished
69    private ActivityStack mRestoreTargetBehindStack;
70
71    RecentsAnimation(ActivityManagerService am, ActivityStackSupervisor stackSupervisor,
72            ActivityStartController activityStartController, WindowManagerService wm,
73            UserController userController, int callingPid) {
74        mService = am;
75        mStackSupervisor = stackSupervisor;
76        mDefaultDisplay = stackSupervisor.getDefaultDisplay();
77        mActivityStartController = activityStartController;
78        mWindowManager = wm;
79        mUserController = userController;
80        mCallingPid = callingPid;
81    }
82
83    void startRecentsActivity(Intent intent, IRecentsAnimationRunner recentsAnimationRunner,
84            ComponentName recentsComponent, int recentsUid,
85            IAssistDataReceiver assistDataReceiver) {
86        if (DEBUG) Slog.d(TAG, "startRecentsActivity(): intent=" + intent
87                + " assistDataReceiver=" + assistDataReceiver);
88        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "RecentsAnimation#startRecentsActivity");
89
90        if (!mWindowManager.canStartRecentsAnimation()) {
91            notifyAnimationCancelBeforeStart(recentsAnimationRunner);
92            if (DEBUG) Slog.d(TAG, "Can't start recents animation, nextAppTransition="
93                        + mWindowManager.getPendingAppTransition());
94            return;
95        }
96
97        // If the activity is associated with the recents stack, then try and get that first
98        mTargetActivityType = intent.getComponent() != null
99                && recentsComponent.equals(intent.getComponent())
100                        ? ACTIVITY_TYPE_RECENTS
101                        : ACTIVITY_TYPE_HOME;
102        final ActivityStack targetStack = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED,
103                mTargetActivityType);
104        ActivityRecord targetActivity = getTargetActivity(targetStack, intent.getComponent());
105        final boolean hasExistingActivity = targetActivity != null;
106        if (hasExistingActivity) {
107            final ActivityDisplay display = targetActivity.getDisplay();
108            mRestoreTargetBehindStack = display.getStackAbove(targetStack);
109            if (mRestoreTargetBehindStack == null) {
110                notifyAnimationCancelBeforeStart(recentsAnimationRunner);
111                if (DEBUG) Slog.d(TAG, "No stack above target stack=" + targetStack);
112                return;
113            }
114        }
115
116        // Send launch hint if we are actually launching the target. If it's already visible
117        // (shouldn't happen in general) we don't need to send it.
118        if (targetActivity == null || !targetActivity.visible) {
119            mStackSupervisor.sendPowerHintForLaunchStartIfNeeded(true /* forceSend */,
120                    targetActivity);
121        }
122
123        mStackSupervisor.getActivityMetricsLogger().notifyActivityLaunching();
124
125        mService.setRunningRemoteAnimation(mCallingPid, true);
126
127        mWindowManager.deferSurfaceLayout();
128        try {
129            // Kick off the assist data request in the background before showing the target activity
130            if (assistDataReceiver != null) {
131                final AppOpsManager appOpsManager = (AppOpsManager)
132                        mService.mContext.getSystemService(Context.APP_OPS_SERVICE);
133                final AssistDataReceiverProxy proxy = new AssistDataReceiverProxy(
134                        assistDataReceiver, recentsComponent.getPackageName());
135                mAssistDataRequester = new AssistDataRequester(mService.mContext, mService,
136                        mWindowManager, appOpsManager, proxy, this, OP_ASSIST_STRUCTURE, OP_NONE);
137                mAssistDataRequester.requestAssistData(mStackSupervisor.getTopVisibleActivities(),
138                        true /* fetchData */, false /* fetchScreenshots */,
139                        true /* allowFetchData */, false /* allowFetchScreenshots */,
140                        recentsUid, recentsComponent.getPackageName());
141            }
142
143            if (hasExistingActivity) {
144                // Move the recents activity into place for the animation if it is not top most
145                mDefaultDisplay.moveStackBehindBottomMostVisibleStack(targetStack);
146                if (DEBUG) Slog.d(TAG, "Moved stack=" + targetStack + " behind stack="
147                            + mDefaultDisplay.getStackAbove(targetStack));
148
149                // If there are multiple tasks in the target stack (ie. the home stack, with 3p
150                // and default launchers coexisting), then move the task to the top as a part of
151                // moving the stack to the front
152                if (targetStack.topTask() != targetActivity.getTask()) {
153                    targetStack.addTask(targetActivity.getTask(), true /* toTop */,
154                            "startRecentsActivity");
155                }
156            } else {
157                // No recents activity
158                ActivityOptions options = ActivityOptions.makeBasic();
159                options.setLaunchActivityType(mTargetActivityType);
160                options.setAvoidMoveToFront();
161                intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION);
162
163                mActivityStartController
164                        .obtainStarter(intent, "startRecentsActivity_noTargetActivity")
165                        .setCallingUid(recentsUid)
166                        .setCallingPackage(recentsComponent.getPackageName())
167                        .setActivityOptions(SafeActivityOptions.fromBundle(options.toBundle()))
168                        .setMayWait(mUserController.getCurrentUserId())
169                        .execute();
170                mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
171                mWindowManager.executeAppTransition();
172
173                targetActivity = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED,
174                        mTargetActivityType).getTopActivity();
175
176                // TODO: Maybe wait for app to draw in this particular case?
177
178                if (DEBUG) Slog.d(TAG, "Started intent=" + intent);
179            }
180
181            // Mark the target activity as launch-behind to bump its visibility for the
182            // duration of the gesture that is driven by the recents component
183            targetActivity.mLaunchTaskBehind = true;
184
185            // Fetch all the surface controls and pass them to the client to get the animation
186            // started. Cancel any existing recents animation running synchronously (do not hold the
187            // WM lock)
188            mWindowManager.cancelRecentsAnimationSynchronously(REORDER_MOVE_TO_ORIGINAL_POSITION,
189                    "startRecentsActivity");
190            mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
191                    this, mDefaultDisplay.mDisplayId,
192                    mStackSupervisor.mRecentTasks.getRecentTaskIds());
193
194            // If we updated the launch-behind state, update the visibility of the activities after
195            // we fetch the visible tasks to be controlled by the animation
196            mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
197
198            mStackSupervisor.getActivityMetricsLogger().notifyActivityLaunched(START_TASK_TO_FRONT,
199                    targetActivity);
200
201            // Register for stack order changes
202            mDefaultDisplay.registerStackOrderChangedListener(this);
203        } catch (Exception e) {
204            Slog.e(TAG, "Failed to start recents activity", e);
205            throw e;
206        } finally {
207            mWindowManager.continueSurfaceLayout();
208            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
209        }
210    }
211
212    private void finishAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
213        synchronized (mService) {
214            if (DEBUG) Slog.d(TAG, "onAnimationFinished(): controller="
215                    + mWindowManager.getRecentsAnimationController()
216                    + " reorderMode=" + reorderMode);
217
218            // Cancel the associated assistant data request
219            if (mAssistDataRequester != null) {
220                mAssistDataRequester.cancel();
221                mAssistDataRequester = null;
222            }
223
224            // Unregister for stack order changes
225            mDefaultDisplay.unregisterStackOrderChangedListener(this);
226
227            if (mWindowManager.getRecentsAnimationController() == null) return;
228
229            // Just to be sure end the launch hint in case the target activity was never launched.
230            // However, if we're keeping the activity and making it visible, we can leave it on.
231            if (reorderMode != REORDER_KEEP_IN_PLACE) {
232                mStackSupervisor.sendPowerHintForLaunchEndIfNeeded();
233            }
234
235            mService.setRunningRemoteAnimation(mCallingPid, false);
236
237            mWindowManager.inSurfaceTransaction(() -> {
238                Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
239                        "RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
240                mWindowManager.deferSurfaceLayout();
241                try {
242                    mWindowManager.cleanupRecentsAnimation(reorderMode);
243
244                    final ActivityStack targetStack = mDefaultDisplay.getStack(
245                            WINDOWING_MODE_UNDEFINED, mTargetActivityType);
246                    final ActivityRecord targetActivity = targetStack != null
247                            ? targetStack.getTopActivity()
248                            : null;
249                    if (DEBUG) Slog.d(TAG, "onAnimationFinished(): targetStack=" + targetStack
250                            + " targetActivity=" + targetActivity
251                            + " mRestoreTargetBehindStack=" + mRestoreTargetBehindStack);
252                    if (targetActivity == null) {
253                        return;
254                    }
255
256                    // Restore the launched-behind state
257                    targetActivity.mLaunchTaskBehind = false;
258
259                    if (reorderMode == REORDER_MOVE_TO_TOP) {
260                        // Bring the target stack to the front
261                        mStackSupervisor.mNoAnimActivities.add(targetActivity);
262                        targetStack.moveToFront("RecentsAnimation.onAnimationFinished()");
263                        if (DEBUG) {
264                            final ActivityStack topStack = getTopNonAlwaysOnTopStack();
265                            if (topStack != targetStack) {
266                                Slog.w(TAG, "Expected target stack=" + targetStack
267                                        + " to be top most but found stack=" + topStack);
268                            }
269                        }
270                    } else if (reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION){
271                        // Restore the target stack to its previous position
272                        final ActivityDisplay display = targetActivity.getDisplay();
273                        display.moveStackBehindStack(targetStack, mRestoreTargetBehindStack);
274                        if (DEBUG) {
275                            final ActivityStack aboveTargetStack =
276                                    mDefaultDisplay.getStackAbove(targetStack);
277                            if (mRestoreTargetBehindStack != null
278                                    && aboveTargetStack != mRestoreTargetBehindStack) {
279                                Slog.w(TAG, "Expected target stack=" + targetStack
280                                        + " to restored behind stack=" + mRestoreTargetBehindStack
281                                        + " but it is behind stack=" + aboveTargetStack);
282                            }
283                        }
284                    } else {
285                        // Keep target stack in place, nothing changes, so ignore the transition
286                        // logic below
287                        return;
288                    }
289
290                    mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
291                    mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, false);
292                    mStackSupervisor.resumeFocusedStackTopActivityLocked();
293
294                    // No reason to wait for the pausing activity in this case, as the hiding of
295                    // surfaces needs to be done immediately.
296                    mWindowManager.executeAppTransition();
297
298                    // After reordering the stacks, reset the minimized state. At this point, either
299                    // the target activity is now top-most and we will stay minimized (if in
300                    // split-screen), or we will have returned to the app, and the minimized state
301                    // should be reset
302                    mWindowManager.checkSplitScreenMinimizedChanged(true /* animate */);
303                } catch (Exception e) {
304                    Slog.e(TAG, "Failed to clean up recents activity", e);
305                    throw e;
306                } finally {
307                    mWindowManager.continueSurfaceLayout();
308                    Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
309                }
310            });
311        }
312    }
313
314    @Override
315    public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode,
316            boolean runSychronously) {
317        if (runSychronously) {
318            finishAnimation(reorderMode);
319        } else {
320            mService.mHandler.post(() -> finishAnimation(reorderMode));
321        }
322    }
323
324    @Override
325    public void onStackOrderChanged() {
326        // If the activity display stack order changes, cancel any running recents animation in
327        // place
328        mWindowManager.cancelRecentsAnimationSynchronously(REORDER_KEEP_IN_PLACE,
329                "stackOrderChanged");
330    }
331
332    /**
333     * Called only when the animation should be canceled prior to starting.
334     */
335    private void notifyAnimationCancelBeforeStart(IRecentsAnimationRunner recentsAnimationRunner) {
336        try {
337            recentsAnimationRunner.onAnimationCanceled();
338        } catch (RemoteException e) {
339            Slog.e(TAG, "Failed to cancel recents animation before start", e);
340        }
341    }
342
343    /**
344     * @return The top stack that is not always-on-top.
345     */
346    private ActivityStack getTopNonAlwaysOnTopStack() {
347        for (int i = mDefaultDisplay.getChildCount() - 1; i >= 0; i--) {
348            final ActivityStack s = mDefaultDisplay.getChildAt(i);
349            if (s.getWindowConfiguration().isAlwaysOnTop()) {
350                continue;
351            }
352            return s;
353        }
354        return null;
355    }
356
357    /**
358     * @return the top activity in the {@param targetStack} matching the {@param component}, or just
359     * the top activity of the top task if no task matches the component.
360     */
361    private ActivityRecord getTargetActivity(ActivityStack targetStack, ComponentName component) {
362        if (targetStack == null) {
363            return null;
364        }
365
366        for (int i = targetStack.getChildCount() - 1; i >= 0; i--) {
367            final TaskRecord task = (TaskRecord) targetStack.getChildAt(i);
368            if (task.getBaseIntent().getComponent().equals(component)) {
369                return task.getTopActivity();
370            }
371        }
372        return targetStack.getTopActivity();
373    }
374}
375