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

Commit 68fbb472 authored by Evan Rosky's avatar Evan Rosky
Browse files

Basic support for split-screen in new transition system

Sends the "trigger" taskinfo associated with a transition when
applicable. This can be used by the transitionplayer to make
decisions about how to start and play transitions.

Additionally, TaskInfo is provided in each Change object if
they are tasks. This provides all the information needed to
choose how to handle split.

Shell's transitionplayer makes use of this to add support for
"handlers" to deal with specific transition requests. Currently
a single handler is added to process split-screen transitions.

Bug: 169035082
Test: Use split-screen (enter, exit via swipe up/down,
      exit via dismiss top)
Change-Id: I53a03dd85cb260505e66d5d87a34afa4ab1f8cc8
parent a37c8403
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -386,6 +386,16 @@ public interface WindowManager extends ViewManager {
     * @hide
     */
    int TRANSIT_KEYGUARD_UNOCCLUDE = 9;
    /**
     * The first slot for custom transition types. Callers (like Shell) can make use of custom
     * transition types for dealing with special cases. These types are effectively ignored by
     * Core and will just be passed along as part of TransitionInfo objects. An example is
     * split-screen using a custom type for it's snap-to-dismiss action. By using a custom type,
     * Shell can properly dispatch the results of that transition to the split-screen
     * implementation.
     * @hide
     */
    int TRANSIT_FIRST_CUSTOM = 10;

    /**
     * @hide
@@ -401,6 +411,7 @@ public interface WindowManager extends ViewManager {
            TRANSIT_KEYGUARD_GOING_AWAY,
            TRANSIT_KEYGUARD_OCCLUDE,
            TRANSIT_KEYGUARD_UNOCCLUDE,
            TRANSIT_FIRST_CUSTOM
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface TransitionType {}
+5 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.window;

import android.app.ActivityManager;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.WindowContainerTransaction;
@@ -58,6 +59,9 @@ oneway interface ITransitionPlayer {
     * @param type The {@link WindowManager#TransitionType} of the transition to start.
     * @param transitionToken An identifying token for the transition that needs to be started.
     *                        Pass this to {@link IWindowOrganizerController#startTransition}.
     * @param triggerTask If non-null, the task containing the activity whose lifecycle change
     *                    (start or finish) has caused this transition to occur.
     */
    void requestStartTransition(int type, in IBinder transitionToken);
    void requestStartTransition(int type, in IBinder transitionToken,
            in ActivityManager.RunningTaskInfo triggerTask);
}
+21 −2
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Parcel;
@@ -153,7 +154,8 @@ public final class TransitionInfo implements Parcelable {
    /**
     * @return a surfacecontrol that can serve as a parent surfacecontrol for all the changing
     * participants to animate within. This will generally be placed at the highest-z-order
     * shared ancestor of all participants.
     * shared ancestor of all participants. While this is non-null, it's possible for the rootleash
     * to be invalid if the transition is a no-op.
     */
    @NonNull
    public SurfaceControl getRootLeash() {
@@ -181,7 +183,7 @@ public final class TransitionInfo implements Parcelable {
    @Nullable
    public Change getChange(@NonNull WindowContainerToken token) {
        for (int i = mChanges.size() - 1; i >= 0; --i) {
            if (mChanges.get(i).mContainer == token) {
            if (token.equals(mChanges.get(i).mContainer)) {
                return mChanges.get(i);
            }
        }
@@ -254,6 +256,7 @@ public final class TransitionInfo implements Parcelable {
        private final Rect mStartAbsBounds = new Rect();
        private final Rect mEndAbsBounds = new Rect();
        private final Point mEndRelOffset = new Point();
        private ActivityManager.RunningTaskInfo mTaskInfo = null;

        public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) {
            mContainer = container;
@@ -270,6 +273,7 @@ public final class TransitionInfo implements Parcelable {
            mStartAbsBounds.readFromParcel(in);
            mEndAbsBounds.readFromParcel(in);
            mEndRelOffset.readFromParcel(in);
            mTaskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
        }

        /** Sets the parent of this change's container. The parent must be a participant or null. */
@@ -302,6 +306,14 @@ public final class TransitionInfo implements Parcelable {
            mEndRelOffset.set(left, top);
        }

        /**
         * Sets the taskinfo of this container if this is a task. WARNING: this takes the
         * reference, so don't modify it afterwards.
         */
        public void setTaskInfo(ActivityManager.RunningTaskInfo taskInfo) {
            mTaskInfo = taskInfo;
        }

        /** @return the container that is changing. May be null if non-remotable (eg. activity) */
        @Nullable
        public WindowContainerToken getContainer() {
@@ -359,6 +371,12 @@ public final class TransitionInfo implements Parcelable {
            return mLeash;
        }

        /** @return the task info or null if this isn't a task */
        @NonNull
        public ActivityManager.RunningTaskInfo getTaskInfo() {
            return mTaskInfo;
        }

        @Override
        /** @hide */
        public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -370,6 +388,7 @@ public final class TransitionInfo implements Parcelable {
            mStartAbsBounds.writeToParcel(dest, flags);
            mEndAbsBounds.writeToParcel(dest, flags);
            mEndRelOffset.writeToParcel(dest, flags);
            dest.writeTypedObject(mTaskInfo, flags);
        }

        @NonNull
+4 −7
Original line number Diff line number Diff line
@@ -55,19 +55,16 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
                taskInfo.taskId);
        mLeashByTaskId.put(taskInfo.taskId, leash);
        if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
        final Point positionInParent = taskInfo.positionInParent;
        mSyncQueue.runInSync(t -> {
            // Reset several properties back to fullscreen (PiP, for example, leaves all these
            // properties in a bad state).
            t.setWindowCrop(leash, null);
            t.setPosition(leash, positionInParent.x, positionInParent.y);
            // TODO(shell-transitions): Eventually set everything in transition so there's no
            //                          SF Transaction here.
            if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
            t.setAlpha(leash, 1f);
            t.setMatrix(leash, 1, 0, 0, 1);
            t.show(leash);
            }
        });
    }

+166 −35
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.wm.shell;

import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -25,15 +26,17 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.ITransitionPlayer;
import android.window.TransitionInfo;
import android.window.WindowContainerTransaction;
import android.window.WindowOrganizer;

import androidx.annotation.BinderThread;
@@ -59,8 +62,16 @@ public class Transitions {
    private final ShellExecutor mAnimExecutor;
    private final TransitionPlayerImpl mPlayerImpl;

    /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
    private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();

    private static final class ActiveTransition {
        ArrayList<Animator> mAnimations = null;
        TransitionHandler mFirstHandler = null;
    }

    /** Keeps track of currently tracked transitions and all the animations associated with each */
    private final ArrayMap<IBinder, ArrayList<Animator>> mActiveTransitions = new ArrayMap<>();
    private final ArrayMap<IBinder, ActiveTransition> mActiveTransitions = new ArrayMap<>();

    public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
            @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
@@ -75,6 +86,22 @@ public class Transitions {
        taskOrganizer.registerTransitionPlayer(mPlayerImpl);
    }

    /**
     * Adds a handler candidate.
     * @see TransitionHandler
     */
    public void addHandler(@NonNull TransitionHandler handler) {
        mHandlers.add(handler);
    }

    public ShellExecutor getMainExecutor() {
        return mMainExecutor;
    }

    public ShellExecutor getAnimExecutor() {
        return mAnimExecutor;
    }

    // TODO(shell-transitions): real animations
    private void startExampleAnimation(@NonNull IBinder transition, @NonNull SurfaceControl leash,
            boolean show) {
@@ -93,7 +120,7 @@ public class Transitions {
            transaction.apply();
            mTransactionPool.release(transaction);
            mMainExecutor.execute(() -> {
                mActiveTransitions.get(transition).remove(va);
                mActiveTransitions.get(transition).mAnimations.remove(va);
                onFinish(transition);
            });
        };
@@ -114,30 +141,23 @@ public class Transitions {
            @Override
            public void onAnimationRepeat(Animator animation) { }
        });
        mActiveTransitions.get(transition).add(va);
        mActiveTransitions.get(transition).mAnimations.add(va);
        mAnimExecutor.execute(va::start);
    }

    private static boolean isOpeningType(@WindowManager.TransitionType int type) {
    /** @return true if the transition was triggered by opening something vs closing something */
    public static boolean isOpeningType(@WindowManager.TransitionType int type) {
        return type == TRANSIT_OPEN
                || type == TRANSIT_TO_FRONT
                || type == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
    }

    private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
    /**
     * Reparents all participants into a shared parent and orders them based on: the global transit
     * type, their transit mode, and their destination z-order.
     */
    private static void setupStartState(@NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction t) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
                transitionToken, info);
        // start task
        if (!mActiveTransitions.containsKey(transitionToken)) {
            Slog.e(TAG, "Got transitionReady for non-active transition " + transitionToken
                    + " expecting one of " + mActiveTransitions.keySet());
        }
        if (mActiveTransitions.get(transitionToken) != null) {
            throw new IllegalStateException("Got a duplicate onTransitionReady call for "
                    + transitionToken);
        }
        mActiveTransitions.put(transitionToken, new ArrayList<>());
        boolean isOpening = isOpeningType(info.getType());
        if (info.getRootLeash().isValid()) {
            t.show(info.getRootLeash());
@@ -148,24 +168,26 @@ public class Transitions {
            final SurfaceControl leash = change.getLeash();
            final int mode = info.getChanges().get(i).getMode();

            // Don't animate anything with an animating parent
            // Don't move anything with an animating parent
            if (change.getParent() != null) {
                if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
                if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) {
                    t.show(leash);
                    t.setMatrix(leash, 1, 0, 0, 1);
                    t.setAlpha(leash, 1.f);
                    t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
                }
                continue;
            }

            t.reparent(leash, info.getRootLeash());
            t.setPosition(leash, change.getEndAbsBounds().left - info.getRootOffset().x,
                    change.getEndAbsBounds().top - info.getRootOffset().y);
            t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
                    change.getStartAbsBounds().top - info.getRootOffset().y);
            // Put all the OPEN/SHOW on top
            if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
                t.show(leash);
                t.setMatrix(leash, 1, 0, 0, 1);
                if (isOpening) {
                    // put on top and fade in
                    // put on top with 0 alpha
                    t.setLayer(leash, info.getChanges().size() - i);
                    if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
                        // This received a transferred starting window, so make it immediately
@@ -173,47 +195,155 @@ public class Transitions {
                        t.setAlpha(leash, 1.f);
                    } else {
                        t.setAlpha(leash, 0.f);
                        startExampleAnimation(transitionToken, leash, true /* show */);
                    }
                } else {
                    // put on bottom and leave it visible without fade
                    // put on bottom and leave it visible
                    t.setLayer(leash, -i);
                    t.setAlpha(leash, 1.f);
                }
            } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
                if (isOpening) {
                    // put on bottom and leave visible without fade
                    // put on bottom and leave visible
                    t.setLayer(leash, -i);
                } else {
                    // put on top and fade out
                    // put on top
                    t.setLayer(leash, info.getChanges().size() - i);
                    startExampleAnimation(transitionToken, leash, false /* show */);
                }
            } else {
            } else { // CHANGE
                t.setLayer(leash, info.getChanges().size() - i);
            }
        }
    }

    private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction t) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
                transitionToken, info);
        final ActiveTransition active = mActiveTransitions.get(transitionToken);
        if (active == null) {
            throw new IllegalStateException("Got transitionReady for non-active transition "
                    + transitionToken + ". expecting one of " + mActiveTransitions.keySet());
        }
        if (active.mAnimations != null) {
            throw new IllegalStateException("Got a duplicate onTransitionReady call for "
                    + transitionToken);
        }
        if (!info.getRootLeash().isValid()) {
            // Invalid root-leash implies that the transition is empty/no-op, so just do
            // housekeeping and return.
            t.apply();
            onFinish(transitionToken);
            return;
        }

        setupStartState(info, t);

        final Runnable finishRunnable = () -> onFinish(transitionToken);
        // If a handler chose to uniquely run this animation, try delegating to it.
        if (active.mFirstHandler != null && active.mFirstHandler.startAnimation(
                transitionToken, info, t, finishRunnable)) {
            return;
        }
        // Otherwise give every other handler a chance (in order)
        for (int i = mHandlers.size() - 1; i >= 0; --i) {
            if (mHandlers.get(i) == active.mFirstHandler) continue;
            if (mHandlers.get(i).startAnimation(transitionToken, info, t, finishRunnable)) {
                return;
            }
        }

        // No handler chose to perform this animation, so fall-back to the
        // default animation handling.
        final boolean isOpening = isOpeningType(info.getType());
        active.mAnimations = new ArrayList<>(); // Play fade animations
        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
            final TransitionInfo.Change change = info.getChanges().get(i);

            // Don't animate anything with an animating parent
            if (change.getParent() != null) continue;

            final int mode = info.getChanges().get(i).getMode();
            if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
                if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
                    // This received a transferred starting window, so don't animate
                    continue;
                }
                // fade in
                startExampleAnimation(transitionToken, change.getLeash(), true /* show */);
            } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
                // fade out
                startExampleAnimation(transitionToken, change.getLeash(), false /* show */);
            }
        }
        t.apply();
        onFinish(transitionToken);
    }

    private void onFinish(IBinder transition) {
        if (!mActiveTransitions.get(transition).isEmpty()) return;
        final ActiveTransition active = mActiveTransitions.get(transition);
        if (active.mAnimations != null && !active.mAnimations.isEmpty()) return;
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                "Transition animations finished, notifying core %s", transition);
        mActiveTransitions.remove(transition);
        mOrganizer.finishTransition(transition, null, null);
    }

    private void requestStartTransition(int type, @NonNull IBinder transitionToken) {
    private void requestStartTransition(int type, @NonNull IBinder transitionToken,
            @Nullable ActivityManager.RunningTaskInfo triggerTask) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s",
                type, transitionToken);

        if (mActiveTransitions.containsKey(transitionToken)) {
            throw new RuntimeException("Transition already started " + transitionToken);
        }
        IBinder transition = mOrganizer.startTransition(type, transitionToken, null /* wct */);
        mActiveTransitions.put(transition, null);
        final ActiveTransition active = new ActiveTransition();
        WindowContainerTransaction wct = null;
        for (int i = mHandlers.size() - 1; i >= 0; --i) {
            wct = mHandlers.get(i).handleRequest(type, transitionToken, triggerTask);
            if (wct != null) {
                active.mFirstHandler = mHandlers.get(i);
                break;
            }
        }
        IBinder transition = mOrganizer.startTransition(type, transitionToken, wct);
        mActiveTransitions.put(transition, active);
    }

    /** Start a new transition directly. */
    public IBinder startTransition(@WindowManager.TransitionType int type,
            @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
        final ActiveTransition active = new ActiveTransition();
        active.mFirstHandler = handler;
        IBinder transition = mOrganizer.startTransition(type, null /* token */, wct);
        mActiveTransitions.put(transition, active);
        return transition;
    }

    /**
     * Interface for something which can handle a subset of transitions.
     */
    public interface TransitionHandler {
        /**
         * Starts a transition animation. This is always called if handleRequest returned non-null
         * for a particular transition. Otherwise, it is only called if no other handler before
         * it handled the transition.
         *
         * @return true if transition was handled, false if not (falls-back to default).
         */
        boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
                @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback);

        /**
         * Potentially handles a startTransition request.
         * @param type The transition type
         * @param triggerTask The task which triggered this transition request.
         * @return WCT to apply with transition-start or null if this handler isn't handling
         *         the request.
         */
        @Nullable
        WindowContainerTransaction handleRequest(@WindowManager.TransitionType int type,
                @NonNull IBinder transition,
                @Nullable ActivityManager.RunningTaskInfo triggerTask);
    }

    @BinderThread
@@ -227,9 +357,10 @@ public class Transitions {
        }

        @Override
        public void requestStartTransition(int i, IBinder iBinder) throws RemoteException {
        public void requestStartTransition(int i, IBinder iBinder,
                ActivityManager.RunningTaskInfo runningTaskInfo) throws RemoteException {
            mMainExecutor.execute(() -> {
                Transitions.this.requestStartTransition(i, iBinder);
                Transitions.this.requestStartTransition(i, iBinder, runningTaskInfo);
            });
        }
    }
Loading