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

Commit 4fb50c30 authored by Winson Chung's avatar Winson Chung
Browse files

Add simple mechanism for synthetic recents transitions

- Only the simplest flow to unblock development, this synthetic transition
  path is immediately interrupted when any real transition is started
- Also remove a bunch of unused code

Bug: 366021931
Flag: EXEMPT (unused without enableFallbackOverviewInWindow)
Tests: atest RecentsTransitionHandlerTests
Change-Id: I4444fef508e6477685c71d4798318273039b1af8
parent ad216bac
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.graphics.Rect;
import android.util.ArrayMap;
@@ -339,6 +340,52 @@ public class TransitionUtil {
        return target;
    }

    /**
     * Creates a new RemoteAnimationTarget from the provided change and leash
     */
    public static RemoteAnimationTarget newSyntheticTarget(ActivityManager.RunningTaskInfo taskInfo,
            SurfaceControl leash, @TransitionInfo.TransitionMode int mode, int order,
            boolean isTranslucent) {
        int taskId;
        boolean isNotInRecents;
        WindowConfiguration windowConfiguration;

        if (taskInfo != null) {
            taskId = taskInfo.taskId;
            isNotInRecents = !taskInfo.isRunning;
            windowConfiguration = taskInfo.configuration.windowConfiguration;
        } else {
            taskId = INVALID_TASK_ID;
            isNotInRecents = true;
            windowConfiguration = new WindowConfiguration();
        }

        Rect localBounds = new Rect();
        RemoteAnimationTarget target = new RemoteAnimationTarget(
                taskId,
                newModeToLegacyMode(mode),
                // TODO: once we can properly sync transactions across process,
                // then get rid of this leash.
                leash,
                isTranslucent,
                null,
                // TODO(shell-transitions): we need to send content insets? evaluate how its used.
                new Rect(0, 0, 0, 0),
                order,
                null,
                localBounds,
                new Rect(),
                windowConfiguration,
                isNotInRecents,
                null,
                new Rect(),
                taskInfo,
                false,
                INVALID_WINDOW_TYPE
        );
        return target;
    }

    private static RemoteAnimationTarget getDividerTarget(TransitionInfo.Change change,
            SurfaceControl leash) {
        return new RemoteAnimationTarget(-1 /* taskId */, newModeToLegacyMode(change.getMode()),
+0 −16
Original line number Diff line number Diff line
@@ -46,7 +46,6 @@ import android.util.Log;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.ITaskOrganizerController;
import android.window.ScreenCapture;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
@@ -55,7 +54,6 @@ import android.window.TaskOrganizer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.api.CompatUIHandler;
@@ -74,7 +72,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;

/**
 * Unified task organizer for all components in the shell.
@@ -561,19 +558,6 @@ public class ShellTaskOrganizer extends TaskOrganizer {
        mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskAdded(info.getTaskInfo()));
    }

    /**
     * Take a screenshot of a task.
     */
    public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
            Consumer<ScreenCapture.ScreenshotHardwareBuffer> consumer) {
        final TaskAppearedInfo info = mTasks.get(taskInfo.taskId);
        if (info == null) {
            return;
        }
        ScreenshotUtils.captureLayer(info.getLeash(), crop, consumer);
    }


    @Override
    public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
        synchronized (mLock) {
+2 −1
Original line number Diff line number Diff line
@@ -474,10 +474,11 @@ public abstract class WMShellModule {
    @Provides
    static RecentsTransitionHandler provideRecentsTransitionHandler(
            ShellInit shellInit,
            ShellTaskOrganizer shellTaskOrganizer,
            Transitions transitions,
            Optional<RecentTasksController> recentTasksController,
            HomeTransitionObserver homeTransitionObserver) {
        return new RecentsTransitionHandler(shellInit, transitions,
        return new RecentsTransitionHandler(shellInit, shellTaskOrganizer, transitions,
                recentTasksController.orElse(null), homeTransitionObserver);
    }

+215 −31
Original line number Diff line number Diff line
@@ -20,9 +20,12 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -41,6 +44,7 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -64,6 +68,7 @@ import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -79,10 +84,15 @@ import java.util.function.Consumer;
 * Handles the Recents (overview) animation. Only one of these can run at a time. A recents
 * transition must be created via {@link #startRecentsTransition}. Anything else will be ignored.
 */
public class RecentsTransitionHandler implements Transitions.TransitionHandler {
public class RecentsTransitionHandler implements Transitions.TransitionHandler,
        Transitions.TransitionObserver {
    private static final String TAG = "RecentsTransitionHandler";

    // A placeholder for a synthetic transition that isn't backed by a true system transition
    public static final IBinder SYNTHETIC_TRANSITION = new Binder();

    private final Transitions mTransitions;
    private final ShellTaskOrganizer mShellTaskOrganizer;
    private final ShellExecutor mExecutor;
    @Nullable
    private final RecentTasksController mRecentTasksController;
@@ -99,19 +109,26 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
    private final HomeTransitionObserver mHomeTransitionObserver;
    private @Nullable Color mBackgroundColor;

    public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions,
    public RecentsTransitionHandler(
            @NonNull ShellInit shellInit,
            @NonNull ShellTaskOrganizer shellTaskOrganizer,
            @NonNull Transitions transitions,
            @Nullable RecentTasksController recentTasksController,
            HomeTransitionObserver homeTransitionObserver) {
            @NonNull HomeTransitionObserver homeTransitionObserver) {
        mShellTaskOrganizer = shellTaskOrganizer;
        mTransitions = transitions;
        mExecutor = transitions.getMainExecutor();
        mRecentTasksController = recentTasksController;
        mHomeTransitionObserver = homeTransitionObserver;
        if (!Transitions.ENABLE_SHELL_TRANSITIONS) return;
        if (recentTasksController == null) return;
        shellInit.addInitCallback(() -> {
            recentTasksController.setTransitionHandler(this);
            transitions.addHandler(this);
        }, this);
        shellInit.addInitCallback(this::onInit, this);
    }

    private void onInit() {
        mRecentTasksController.setTransitionHandler(this);
        mTransitions.addHandler(this);
        mTransitions.registerObserver(this);
    }

    /** Register a mixer handler. {@see RecentsMixedHandler}*/
@@ -138,17 +155,59 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
        mBackgroundColor = color;
    }

    /**
     * Starts a new real/synthetic recents transition.
     */
    @VisibleForTesting
    public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
            IApplicationThread appThread, IRecentsAnimationRunner listener) {
        // only care about latest one.
        mAnimApp = appThread;

        // TODO(b/366021931): Formalize this later
        final boolean isSyntheticRequest = options.containsKey("is_synthetic_recents_transition");
        if (isSyntheticRequest) {
            return startSyntheticRecentsTransition(listener);
        } else {
            return startRealRecentsTransition(intent, fillIn, options, listener);
        }
    }

    /**
     * Starts a synthetic recents transition that is not backed by a real WM transition.
     */
    private IBinder startSyntheticRecentsTransition(@NonNull IRecentsAnimationRunner listener) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                "RecentsTransitionHandler.startRecentsTransition(synthetic)");
        final RecentsController lastController = getLastController();
        if (lastController != null) {
            lastController.cancel(lastController.isSyntheticTransition()
                    ? "existing_running_synthetic_transition"
                    : "existing_running_transition");
            return null;
        }

        // Create a new synthetic transition and start it immediately
        final RecentsController controller = new RecentsController(listener);
        controller.startSyntheticTransition();
        mControllers.add(controller);
        return SYNTHETIC_TRANSITION;
    }

    /**
     * Starts a real WM-backed recents transition.
     */
    private IBinder startRealRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
            IRecentsAnimationRunner listener) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                "RecentsTransitionHandler.startRecentsTransition");

        // only care about latest one.
        mAnimApp = appThread;
        WindowContainerTransaction wct = new WindowContainerTransaction();
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        wct.sendPendingIntent(intent, fillIn, options);
        final RecentsController controller = new RecentsController(listener);

        // Find the mixed handler which should handle this request (if we are in a state where a
        // mixed handler is needed).  This is slightly convoluted because starting the transition
        // requires the handler, but the mixed handler also needs a reference to the transition.
        RecentsMixedHandler mixer = null;
        Consumer<IBinder> setTransitionForMixer = null;
        for (int i = 0; i < mMixers.size(); ++i) {
@@ -160,12 +219,11 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
        }
        final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct,
                mixer == null ? this : mixer);
        for (int i = 0; i < mStateListeners.size(); i++) {
            mStateListeners.get(i).onTransitionStarted(transition);
        }
        if (mixer != null) {
            setTransitionForMixer.accept(transition);
        }

        final RecentsController controller = new RecentsController(listener);
        if (transition != null) {
            controller.setTransition(transition);
            mControllers.add(controller);
@@ -187,11 +245,28 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
        return null;
    }

    private int findController(IBinder transition) {
    /**
     * Returns if there is currently a pending or active recents transition.
     */
    @Nullable
    private RecentsController getLastController() {
        return !mControllers.isEmpty() ? mControllers.getLast() : null;
    }

    /**
     * Finds an existing controller for the provided {@param transition}, or {@code null} if none
     * exists.
     */
    @Nullable
    @VisibleForTesting
    RecentsController findController(@NonNull IBinder transition) {
        for (int i = mControllers.size() - 1; i >= 0; --i) {
            if (mControllers.get(i).mTransition == transition) return i;
            final RecentsController controller = mControllers.get(i);
            if (controller.mTransition == transition) {
                return controller;
            }
        return -1;
        }
        return null;
    }

    @Override
@@ -199,13 +274,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
            SurfaceControl.Transaction startTransaction,
            SurfaceControl.Transaction finishTransaction,
            Transitions.TransitionFinishCallback finishCallback) {
        final int controllerIdx = findController(transition);
        if (controllerIdx < 0) {
        final RecentsController controller = findController(transition);
        if (controller == null) {
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                    "RecentsTransitionHandler.startAnimation: no controller found");
            return false;
        }
        final RecentsController controller = mControllers.get(controllerIdx);
        final IApplicationThread animApp = mAnimApp;
        mAnimApp = null;
        if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) {
@@ -221,13 +295,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
    public void mergeAnimation(IBinder transition, TransitionInfo info,
            SurfaceControl.Transaction t, IBinder mergeTarget,
            Transitions.TransitionFinishCallback finishCallback) {
        final int targetIdx = findController(mergeTarget);
        if (targetIdx < 0) {
        final RecentsController controller = findController(mergeTarget);
        if (controller == null) {
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                    "RecentsTransitionHandler.mergeAnimation: no controller found");
            return;
        }
        final RecentsController controller = mControllers.get(targetIdx);
        controller.merge(info, t, finishCallback);
    }

@@ -244,8 +317,21 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
        }
    }

    @Override
    public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction) {
        RecentsController controller = findController(SYNTHETIC_TRANSITION);
        if (controller != null) {
            // Cancel the existing synthetic transition if there is one
            controller.cancel("incoming_transition");
        }
    }

    /** There is only one of these and it gets reset on finish. */
    private class RecentsController extends IRecentsAnimationController.Stub {
    @VisibleForTesting
    class RecentsController extends IRecentsAnimationController.Stub {

        private final int mInstanceId;

        private IRecentsAnimationRunner mListener;
@@ -307,7 +393,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
            mDeathHandler = () -> {
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                        "[%d] RecentsController.DeathRecipient: binder died", mInstanceId);
                finish(mWillFinishToHome, false /* leaveHint */, null /* finishCb */);
                finishInner(mWillFinishToHome, false /* leaveHint */, null /* finishCb */,
                        "deathRecipient");
            };
            try {
                mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */);
@@ -317,6 +404,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
            }
        }

        /**
         * Sets the started transition for this instance of the recents transition.
         */
        void setTransition(IBinder transition) {
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                    "[%d] RecentsController.setTransition: id=%s", mInstanceId, transition);
@@ -330,6 +420,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
        }

        void cancel(boolean toHome, boolean withScreenshots, String reason) {
            if (cancelSyntheticTransition(reason)) {
                return;
            }

            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                    "[%d] RecentsController.cancel: toHome=%b reason=%s",
                    mInstanceId, toHome, reason);
@@ -341,7 +435,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
                }
            }
            if (mFinishCB != null) {
                finishInner(toHome, false /* userLeave */, null /* finishCb */);
                finishInner(toHome, false /* userLeave */, null /* finishCb */, "cancel");
            } else {
                cleanUp();
            }
@@ -436,6 +530,91 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
            }
        }

        /**
         * Starts a new transition that is not backed by a system transition.
         */
        void startSyntheticTransition() {
            mTransition = SYNTHETIC_TRANSITION;

            // TODO(b/366021931): Update mechanism for pulling the home task, for now add home as
            //                    both opening and closing since there's some pre-existing
            //                    dependencies on having a closing task
            final ActivityManager.RunningTaskInfo homeTask =
                    mShellTaskOrganizer.getRunningTasks(DEFAULT_DISPLAY).stream()
                            .filter(task -> task.getActivityType() == ACTIVITY_TYPE_HOME)
                            .findFirst()
                            .get();
            final RemoteAnimationTarget openingTarget = TransitionUtil.newSyntheticTarget(
                    homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_OPEN,
                    0, true /* isTranslucent */);
            final RemoteAnimationTarget closingTarget = TransitionUtil.newSyntheticTarget(
                    homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_CLOSE,
                    0, true /* isTranslucent */);
            final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
            apps.add(openingTarget);
            apps.add(closingTarget);
            try {
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                        "[%d] RecentsController.start: calling onAnimationStart with %d apps",
                        mInstanceId, apps.size());
                mListener.onAnimationStart(this,
                        apps.toArray(new RemoteAnimationTarget[apps.size()]),
                        new RemoteAnimationTarget[0],
                        new Rect(0, 0, 0, 0), new Rect(), new Bundle());
                for (int i = 0; i < mStateListeners.size(); i++) {
                    mStateListeners.get(i).onAnimationStateChanged(true);
                }
            } catch (RemoteException e) {
                Slog.e(TAG, "Error starting recents animation", e);
                cancel("startSynthetricTransition() failed");
            }
        }

        /**
         * Returns whether this transition is backed by a real system transition or not.
         */
        boolean isSyntheticTransition() {
            return mTransition == SYNTHETIC_TRANSITION;
        }

        /**
         * Called when a synthetic transition is canceled.
         */
        boolean cancelSyntheticTransition(String reason) {
            if (!isSyntheticTransition()) {
                return false;
            }

            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                    "[%d] RecentsController.cancelSyntheticTransition reason=%s",
                    mInstanceId, reason);
            try {
                // TODO(b/366021931): Notify the correct tasks once we build actual targets, and
                //                    clean up leashes accordingly
                mListener.onAnimationCanceled(new int[0], new TaskSnapshot[0]);
            } catch (RemoteException e) {
                Slog.e(TAG, "Error canceling previous recents animation", e);
            }
            cleanUp();
            return true;
        }

        /**
         * Called when a synthetic transition is finished.
         * @return
         */
        boolean finishSyntheticTransition() {
            if (!isSyntheticTransition()) {
                return false;
            }

            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                    "[%d] RecentsController.finishSyntheticTransition", mInstanceId);
            // TODO(b/366021931): Clean up leashes accordingly
            cleanUp();
            return true;
        }

        boolean start(TransitionInfo info, SurfaceControl.Transaction t,
                SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) {
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
@@ -662,7 +841,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
                            // Set the callback once again so we can finish correctly.
                            mFinishCB = finishCB;
                            finishInner(true /* toHome */, false /* userLeave */,
                                    null /* finishCb */);
                                    null /* finishCb */, "takeOverAnimation");
                        }, updatedStates);
            });
        }
@@ -810,7 +989,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
                sendCancelWithSnapshots();
                mExecutor.executeDelayed(
                        () -> finishInner(true /* toHome */, false /* userLeaveHint */,
                                null /* finishCb */), 0);
                                null /* finishCb */, "merge"), 0);
                return;
            }
            if (recentsOpening != null) {
@@ -1005,7 +1184,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
                    return;
                }
                final int displayId = mInfo.getRootCount() > 0 ? mInfo.getRoot(0).getDisplayId()
                        : Display.DEFAULT_DISPLAY;
                        : DEFAULT_DISPLAY;
                // 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
@@ -1038,11 +1217,16 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
        @Override
        @SuppressLint("NewApi")
        public void finish(boolean toHome, boolean sendUserLeaveHint, IResultReceiver finishCb) {
            mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb));
            mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint, finishCb,
                    "requested"));
        }

        private void finishInner(boolean toHome, boolean sendUserLeaveHint,
                IResultReceiver runnerFinishCb) {
                IResultReceiver runnerFinishCb, String reason) {
            if (finishSyntheticTransition()) {
                return;
            }

            if (mFinishCB == null) {
                Slog.e(TAG, "Duplicate call to finish");
                return;
+0 −3
Original line number Diff line number Diff line
@@ -24,7 +24,4 @@ public interface RecentsTransitionStateListener {
    /** Notifies whether the recents animation is running. */
    default void onAnimationStateChanged(boolean running) {
    }

    /** Notifies that a recents shell transition has started. */
    default void onTransitionStarted(IBinder transition) {}
}
Loading