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

Commit eb299fae authored by Evan Rosky's avatar Evan Rosky Committed by Android (Google) Code Review
Browse files

Merge "Basic support for split-screen in new transition system"

parents 1a3a4a11 68fbb472
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