Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 89e82322 authored by Evan Rosky's avatar Evan Rosky
Browse files

Stabilize overview/quick-switch transitions

When done in rapid succession or at some bad timings,
it was possible to get stuck either with touch being
ignored or with some windows incorrectly invisible.

This is fixed primarily via tracking of task state in
the recents transition animator to account for many
changes being merged into one animation. This includes
a task-switch being interrupted by a re-entering gesture
as well as re-opening of the live-tile due to unsolicited
intents (eg. notification permission activity).

Without detailed tracking, the WMCore state and the
recents animator state would fall out of sync.

Second, make sure that visibility-changes resulting from
`setFocusedTask` calls are done in a transition. This
API was just missed before so Shell wouldn't be notified
of some visibility changes.

Bug: 269691848
Bug: 269691226
Test: existing tests pass. Also manually do rapid quick-switches.
Change-Id: I0b465b11ea316fc670a1ba121e61535e22108be5
parent 27e03dfc
Loading
Loading
Loading
Loading
+124 −59
Original line number Diff line number Diff line
@@ -36,7 +36,6 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
import android.view.IRecentsAnimationController;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -106,12 +105,24 @@ public class RemoteTransitionCompat {
        private RecentsAnimationListener mListener = null;
        private RecentsAnimationControllerCompat mWrapped = null;
        private IRemoteTransitionFinishedCallback mFinishCB = null;

        /**
         * List of tasks that we are switching away from via this transition. Upon finish, these
         * pausing tasks will become invisible.
         * These need to be ordered since the order must be restored if there is no task-switch.
         */
        private ArrayList<TaskState> mPausingTasks = null;

        /**
         * List of tasks that we are switching to. Upon finish, these will remain visible and
         * on top.
         */
        private ArrayList<TaskState> mOpeningTasks = null;

        private WindowContainerToken mPipTask = null;
        private WindowContainerToken mRecentsTask = null;
        private int mRecentsTaskId = 0;
        private TransitionInfo mInfo = null;
        private ArrayList<SurfaceControl> mOpeningLeashes = null;
        private boolean mOpeningSeparateHome = false;
        private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
        private PictureInPictureSurfaceTransaction mPipTransaction = null;
@@ -120,6 +131,15 @@ public class RemoteTransitionCompat {
        private RemoteAnimationTarget[] mAppearedTargets;
        private boolean mWillFinishToHome = false;

        /** The animation is idle, waiting for the user to choose a task to switch to. */
        private static final int STATE_NORMAL = 0;

        /** The user chose a new task to switch to and the animation is animating to it. */
        private static final int STATE_NEW_TASK = 1;

        /** The latest state that the recents animation is operating in. */
        private int mState = STATE_NORMAL;

        void start(RecentsAnimationControllerCompat wrapped, RecentsAnimationListener listener,
                IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
                IRemoteTransitionFinishedCallback finishedCallback) {
@@ -132,12 +152,14 @@ public class RemoteTransitionCompat {
            mInfo = info;
            mFinishCB = finishedCallback;
            mPausingTasks = new ArrayList<>();
            mOpeningTasks = new ArrayList<>();
            mPipTask = null;
            mRecentsTask = null;
            mRecentsTaskId = -1;
            mLeashMap = new ArrayMap<>();
            mTransition = transition;
            mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
            mState = STATE_NORMAL;

            final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
            final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>();
@@ -178,6 +200,9 @@ public class RemoteTransitionCompat {
                    } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
                        mRecentsTask = taskInfo.token;
                        mRecentsTaskId = taskInfo.taskId;
                    } else if (change.getMode() == TRANSIT_OPEN
                            || change.getMode() == TRANSIT_TO_FRONT) {
                        mOpeningTasks.add(new TaskState(change, target.leash));
                    }
                }
            }
@@ -189,34 +214,41 @@ public class RemoteTransitionCompat {

        @SuppressLint("NewApi")
        boolean merge(TransitionInfo info, SurfaceControl.Transaction t) {
            SparseArray<TransitionInfo.Change> openingTasks = null;
            ArrayList<TransitionInfo.Change> openingTasks = null;
            ArrayList<TransitionInfo.Change> closingTasks = null;
            mAppearedTargets = null;
            boolean foundHomeOpening = false;
            mOpeningSeparateHome = false;
            TransitionInfo.Change recentsOpening = null;
            boolean foundRecentsClosing = false;
            boolean hasChangingApp = false;
            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
            final RemoteAnimationTargetCompat.LeafTaskFilter leafTaskFilter =
                    new RemoteAnimationTargetCompat.LeafTaskFilter();
            for (int i = 0; i < info.getChanges().size(); ++i) {
                final TransitionInfo.Change change = info.getChanges().get(i);
                if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
                    if (taskInfo != null) {
                final boolean isLeafTask = leafTaskFilter.test(change);
                if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
                    if (mRecentsTask.equals(change.getContainer())) {
                        recentsOpening = change;
                    } else if (isLeafTask) {
                        if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
                            // This is usually a 3p launcher
                            foundHomeOpening = true;
                            mOpeningSeparateHome = true;
                        }
                        if (openingTasks == null) {
                            openingTasks = new SparseArray<>();
                        }
                        if (taskInfo.hasParentTask()) {
                            // Collects opening leaf tasks only since Launcher monitors leaf task
                            // ids to perform recents animation.
                            openingTasks.remove(taskInfo.parentTaskId);
                            openingTasks = new ArrayList<>();
                        }
                        openingTasks.put(taskInfo.taskId, change);
                        openingTasks.add(change);
                    }
                } else if (change.getMode() == TRANSIT_CLOSE
                        || change.getMode() == TRANSIT_TO_BACK) {
                    if (mRecentsTask.equals(change.getContainer())) {
                        foundRecentsClosing = true;
                    } else if (isLeafTask) {
                        if (closingTasks == null) {
                            closingTasks = new ArrayList<>();
                        }
                        closingTasks.add(change);
                    }
                } else if (change.getMode() == TRANSIT_CHANGE) {
                    hasChangingApp = true;
@@ -234,45 +266,72 @@ public class RemoteTransitionCompat {
                }
                return false;
            }
            if (openingTasks == null) return false;
            int pauseMatches = 0;
            if (!foundHomeOpening) {
                for (int i = 0; i < openingTasks.size(); ++i) {
                    if (TaskState.indexOf(mPausingTasks, openingTasks.valueAt(i)) >= 0) {
                        ++pauseMatches;
                    }
                }
            }
            if (pauseMatches > 0) {
                if (pauseMatches != mPausingTasks.size()) {
                    // We are not really "returning" properly... something went wrong.
                    throw new IllegalStateException("\"Concelling\" a recents transitions by "
                            + "unpausing " + pauseMatches + " apps after pausing "
                            + mPausingTasks.size() + " apps.");
                }
                // In this case, we are "returning" to an already running app, so just consume
                // the merge and do nothing.
                info.releaseAllSurfaces();
                t.close();
                return true;
            }
            if (recentsOpening != null) {
                // the recents task re-appeared. This happens if the user gestures before the
                // task-switch (NEW_TASK) animation finishes.
                if (mState == STATE_NORMAL) {
                    Log.e(TAG, "Returning to recents while recents is already idle.");
                }
                if (closingTasks == null || closingTasks.size() == 0) {
                    Log.e(TAG, "Returning to recents without closing any opening tasks.");
                }
                // Setup may hide it initially since it doesn't know that overview was still active.
                t.show(recentsOpening.getLeash());
                t.setAlpha(recentsOpening.getLeash(), 1.f);
                mState = STATE_NORMAL;
            }
            boolean didMergeThings = false;
            if (closingTasks != null) {
                // Cancelling a task-switch. Move the tasks back to mPausing from mOpening
                for (int i = 0; i < closingTasks.size(); ++i) {
                    final TransitionInfo.Change change = closingTasks.get(i);
                    int openingIdx = TaskState.indexOf(mOpeningTasks, change);
                    if (openingIdx < 0) {
                        Log.e(TAG, "Back to existing recents animation from an unrecognized "
                                + "task: " + change.getTaskInfo().taskId);
                        continue;
                    }
                    mPausingTasks.add(mOpeningTasks.remove(openingIdx));
                    didMergeThings = true;
                }
            }
            if (openingTasks != null && openingTasks.size() > 0) {
                // Switching to some new tasks, add to mOpening and remove from mPausing. Also,
                // enter NEW_TASK state since this will start the switch-to animation.
                final int layer = mInfo.getChanges().size() * 3;
            mOpeningLeashes = new ArrayList<>();
            mOpeningSeparateHome = foundHomeOpening;
                final RemoteAnimationTarget[] targets =
                        new RemoteAnimationTarget[openingTasks.size()];
                for (int i = 0; i < openingTasks.size(); ++i) {
                final TransitionInfo.Change change = openingTasks.valueAt(i);
                mOpeningLeashes.add(change.getLeash());
                    final TransitionInfo.Change change = openingTasks.get(i);
                    int pausingIdx = TaskState.indexOf(mPausingTasks, change);
                    if (pausingIdx >= 0) {
                        // Something is showing/opening a previously-pausing app.
                        targets[i] = newTarget(change, layer, mPausingTasks.get(pausingIdx).mLeash);
                        mOpeningTasks.add(mPausingTasks.remove(pausingIdx));
                        // Setup hides opening tasks initially, so make it visible again (since we
                        // are already showing it).
                        t.show(change.getLeash());
                        t.setAlpha(change.getLeash(), 1.f);
                    } else {
                        // We are receiving new opening tasks, so convert to onTasksAppeared.
                        targets[i] = newTarget(change, layer, info, t, mLeashMap);
                        t.reparent(targets[i].leash, mInfo.getRootLeash());
                        t.setLayer(targets[i].leash, layer);
                        mOpeningTasks.add(new TaskState(change, targets[i].leash));
                    }
                }
                didMergeThings = true;
                mState = STATE_NEW_TASK;
                mAppearedTargets = targets;
            }
            if (!didMergeThings) {
                // Didn't recognize anything in incoming transition so don't merge it.
                Log.w(TAG, "Don't know how to merge this transition.");
                return false;
            }
            t.apply();
            // not using the incoming anim-only surfaces
            info.releaseAnimSurfaces();
            mAppearedTargets = targets;
            return true;
        }

@@ -300,6 +359,8 @@ public class RemoteTransitionCompat {
            if (enabled) {
                // transient launches don't receive focus automatically. Since we are taking over
                // the gesture now, take focus explicitly.
                // This also moves recents back to top if the user gestured before a switch
                // animation finished.
                try {
                    ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId);
                } catch (RemoteException e) {
@@ -336,8 +397,8 @@ public class RemoteTransitionCompat {
                if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
                else wct.restoreTransientOrder(mRecentsTask);
            }
            if (!toHome && !mWillFinishToHome && mPausingTasks != null && mOpeningLeashes == null) {
                // The gesture went back to opening the app rather than continuing with
            if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
                // The gesture is returning to the pausing-task(s) rather than continuing with
                // recents, so end the transition by moving the app back to the top (and also
                // re-showing it's task).
                for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
@@ -349,25 +410,28 @@ public class RemoteTransitionCompat {
                    wct.restoreTransientOrder(mRecentsTask);
                }
            } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) {
                // Special situaition where 3p launcher was changed during recents (this happens
                // Special situation where 3p launcher was changed during recents (this happens
                // during tapltests...). Here we get both "return to home" AND "home opening".
                // This is basically going home, but we have to restore recents order and also
                // treat the home "pausing" task properly.
                for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
                    final TaskState state = mPausingTasks.get(i);
                // This is basically going home, but we have to restore the recents and home order.
                for (int i = 0; i < mOpeningTasks.size(); ++i) {
                    final TaskState state = mOpeningTasks.get(i);
                    if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
                        // Treat as opening (see above)
                        // Make sure it is on top.
                        wct.reorder(state.mToken, true /* onTop */);
                    }
                    t.show(state.mTaskSurface);
                    } else {
                        // Treat as hiding (see below)
                        t.hide(state.mTaskSurface);
                }
                for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
                    t.hide(mPausingTasks.get(i).mTaskSurface);
                }
                if (!mKeyguardLocked && mRecentsTask != null) {
                    wct.restoreTransientOrder(mRecentsTask);
                }
            } else {
                // The general case: committing to recents, going home, or switching tasks.
                for (int i = 0; i < mOpeningTasks.size(); ++i) {
                    t.show(mOpeningTasks.get(i).mTaskSurface);
                }
                for (int i = 0; i < mPausingTasks.size(); ++i) {
                    if (!sendUserLeaveHint) {
                        // This means recents is not *actually* finishing, so of course we gotta
@@ -391,7 +455,7 @@ public class RemoteTransitionCompat {
            try {
                mFinishCB.onTransitionFinished(wct.isEmpty() ? null : wct, t);
            } catch (RemoteException e) {
                Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e);
                Log.e(TAG, "Failed to call animation finish callback", e);
                t.apply();
            }
            // Only release the non-local created surface references. The animator is responsible
@@ -402,12 +466,13 @@ public class RemoteTransitionCompat {
            mListener = null;
            mFinishCB = null;
            mPausingTasks = null;
            mOpeningTasks = null;
            mAppearedTargets = null;
            mInfo = null;
            mOpeningLeashes = null;
            mOpeningSeparateHome = false;
            mLeashMap = null;
            mTransition = null;
            mState = STATE_NORMAL;
        }

        @Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
+19 −1
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;

import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
@@ -2034,7 +2035,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
            return;
        }

        if (r.moveFocusableActivityToTop("setFocusedTask")) {
        final Transition transition = (getTransitionController().isCollecting()
                || !getTransitionController().isShellTransitionsEnabled()) ? null
                : getTransitionController().createTransition(TRANSIT_TO_FRONT);
        if (transition != null) {
            // Set ready before doing anything. If order does change, then that will set it unready
            // so that we wait for the new lifecycles to complete.
            transition.setReady(task, true /* ready */);
        }
        final boolean movedToTop = r.moveFocusableActivityToTop("setFocusedTask");
        if (movedToTop) {
            if (transition != null) {
                getTransitionController().requestStartTransition(
                        transition, null /* startTask */, null /* remote */, null /* display */);
            }
            mRootWindowContainer.resumeFocusedTasksTopActivities();
        } else if (touchedActivity != null && touchedActivity.isFocusable()) {
            final TaskFragment parent = touchedActivity.getTaskFragment();
@@ -2046,6 +2060,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
                        true /* updateInputWindows */);
            }
        }
        if (transition != null && !movedToTop) {
            // No order changes and focus-changes, alone, aren't captured in transitions.
            transition.abort();
        }
    }

    @Override