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

Commit a9bdce0c authored by An An Yu's avatar An An Yu
Browse files

Polish Enter pip transition from AE split.

Before: We were letting PipTransition animate one TaskFragment and no
one was animating the other one.
After: Separate Pip and AE fullscreen transition request, dispatch to
two Handlers.

Before: https://drive.google.com/file/d/1qch2kK8KYY7Yao1wg2TpKk9M1yDd4v4r/view?usp=sharing
After: https://drive.google.com/file/d/1M2Q2ZW03i9dWdQ4PYXW54jQP2_PimhBD/view?usp=sharing

Bug: 259671254

Test: atest

Change-Id: Ia9bf1e90ea23ca5f127b3effa1f63d2e68aac36c
parent 8e68b9d0
Loading
Loading
Loading
Loading
+32 −16
Original line number Diff line number Diff line
@@ -87,33 +87,28 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
        mTransitions.addHandler(this);
    }

    @Override
    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
        boolean containsEmbeddingSplit = false;
        boolean containsNonEmbeddedChange = false;
        final List<TransitionInfo.Change> changes = info.getChanges();
        for (int i = changes.size() - 1; i >= 0; i--) {
            final TransitionInfo.Change change = changes.get(i);
            if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
                containsNonEmbeddedChange = true;
            } else if (!change.hasFlags(FLAG_FILLS_TASK)) {
    /** Whether ActivityEmbeddingController should animate this transition. */
    public boolean shouldAnimate(@NonNull TransitionInfo info) {
        boolean containsEmbeddingChange = false;
        for (TransitionInfo.Change change : info.getChanges()) {
            if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags(
                    FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
                // Whether the Task contains any ActivityEmbedding split before or after the
                // transition.
                containsEmbeddingSplit = true;
                containsEmbeddingChange = true;
            }
        }
        if (!containsEmbeddingSplit) {
        if (!containsEmbeddingChange) {
            // Let the system to play the default animation if there is no ActivityEmbedding split
            // window. This allows to play the app customized animation when there is no embedding,
            // such as the device is in a folded state.
            return false;
        }
        if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) {

        if (containsNonEmbeddedChange(info) && !handleNonEmbeddedChanges(info.getChanges())) {
            return false;
        }

        final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
        if (options != null
                // Scene-transition will be handled by app side.
@@ -123,6 +118,17 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
            return false;
        }

        return true;
    }

    @Override
    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {

        if (!shouldAnimate(info)) return false;

        // Start ActivityEmbedding animation.
        mTransitionCallbacks.put(transition, finishCallback);
        mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction);
@@ -136,6 +142,16 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
        mAnimationRunner.cancelAnimationFromMerge();
    }

    /** Whether TransitionInfo contains non-ActivityEmbedding embedded window. */
    private boolean containsNonEmbeddedChange(@NonNull TransitionInfo info) {
        for (TransitionInfo.Change change : info.getChanges()) {
            if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
                return true;
            }
        }
        return false;
    }

    private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {
        final Rect nonClosingEmbeddedArea = new Rect();
        for (int i = changes.size() - 1; i >= 0; i--) {
+3 −1
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
@@ -366,11 +367,12 @@ public abstract class WMShellModule {
            KeyguardTransitionHandler keyguardTransitionHandler,
            Optional<DesktopTasksController> desktopTasksController,
            Optional<UnfoldTransitionHandler> unfoldHandler,
            Optional<ActivityEmbeddingController> activityEmbeddingController,
            Transitions transitions) {
        return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
                pipTransitionController, recentsTransitionHandler,
                keyguardTransitionHandler, desktopTasksController,
                unfoldHandler);
                unfoldHandler, activityEmbeddingController);
    }

    @WMSingleton
+83 −2
Original line number Diff line number Diff line
@@ -21,7 +21,9 @@ 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.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;

import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
@@ -43,6 +45,7 @@ import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;

import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -74,6 +77,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
    private final KeyguardTransitionHandler mKeyguardHandler;
    private DesktopTasksController mDesktopTasksController;
    private UnfoldTransitionHandler mUnfoldHandler;
    private ActivityEmbeddingController mActivityEmbeddingController;

    private static class MixedTransition {
        static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
@@ -93,9 +97,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
        /** Recents Transition while in desktop mode. */
        static final int TYPE_RECENTS_DURING_DESKTOP = 6;

        /** Fuld/Unfold transition. */
        /** Fold/Unfold transition. */
        static final int TYPE_UNFOLD = 7;

        /** Enter pip from one of the Activity Embedding windows. */
        static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 8;

        /** The default animation for this mixed transition. */
        static final int ANIM_TYPE_DEFAULT = 0;

@@ -150,7 +157,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
            Optional<RecentsTransitionHandler> recentsHandlerOptional,
            KeyguardTransitionHandler keyguardHandler,
            Optional<DesktopTasksController> desktopTasksControllerOptional,
            Optional<UnfoldTransitionHandler> unfoldHandler) {
            Optional<UnfoldTransitionHandler> unfoldHandler,
            Optional<ActivityEmbeddingController> activityEmbeddingController) {
        mPlayer = player;
        mKeyguardHandler = keyguardHandler;
        if (Transitions.ENABLE_SHELL_TRANSITIONS
@@ -170,6 +178,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
                }
                mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
                mUnfoldHandler = unfoldHandler.orElse(null);
                mActivityEmbeddingController = activityEmbeddingController.orElse(null);
            }, this);
        }
    }
@@ -192,6 +201,16 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
            mPipHandler.augmentRequest(transition, request, out);
            mSplitHandler.addEnterOrExitIfNeeded(request, out);
            return out;
        } else if (request.getType() == TRANSIT_PIP
                && (request.getFlags() & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0 && (
                mActivityEmbeddingController != null)) {
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                    " Got a PiP-enter request from an Activity Embedding split");
            mActiveTransitions.add(new MixedTransition(
                    MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition));
            // Postpone transition splitting to later.
            WindowContainerTransaction out = new WindowContainerTransaction();
            return out;
        } else if (request.getRemoteTransition() != null
                && TransitionUtil.isOpeningType(request.getType())
                && (request.getTriggerTask() == null
@@ -355,6 +374,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
        if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
            return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
                    finishCallback);
        } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
            return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction,
                    finishTransaction, finishCallback);
        } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
            return false;
        } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
@@ -400,6 +422,58 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
        }
    }

    private boolean animateEnterPipFromActivityEmbedding(@NonNull MixedTransition mixed,
            @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
                + "entering PIP from an Activity Embedding window");
        // Split into two transitions (wct)
        TransitionInfo.Change pipChange = null;
        final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */);
        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
            TransitionInfo.Change change = info.getChanges().get(i);
            if (mPipHandler.isEnteringPip(change, info.getType())) {
                if (pipChange != null) {
                    throw new IllegalStateException("More than 1 pip-entering changes in one"
                            + " transition? " + info);
                }
                pipChange = change;
                // going backwards, so remove-by-index is fine.
                everythingElse.getChanges().remove(i);
            }
        }

        final Transitions.TransitionFinishCallback finishCB = (wct) -> {
            --mixed.mInFlightSubAnimations;
            mixed.joinFinishArgs(wct);
            if (mixed.mInFlightSubAnimations > 0) return;
            mActiveTransitions.remove(mixed);
            finishCallback.onTransitionFinished(mixed.mFinishWCT);
        };

        if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) {
            // Fallback to dispatching to other handlers.
            return false;
        }

        // PIP window should always be on the highest Z order.
        if (pipChange != null) {
            mixed.mInFlightSubAnimations = 2;
            mPipHandler.startEnterAnimation(
                    pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE),
                    finishTransaction,
                    finishCB);
        } else {
            mixed.mInFlightSubAnimations = 1;
        }

        mActivityEmbeddingController.startAnimation(mixed.mTransition, everythingElse,
                startTransaction, finishTransaction, finishCB);
        return true;
    }

    private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed,
            @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction,
@@ -811,6 +885,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
                } else {
                    mPipHandler.end();
                }
            } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
                mPipHandler.end();
                mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget,
                        finishCallback);
            } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
                mPipHandler.end();
                if (mixed.mLeftoversHandler != null) {
@@ -851,6 +929,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
        if (mixed == null) return;
        if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
            mPipHandler.onTransitionConsumed(transition, aborted, finishT);
        } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
            mPipHandler.onTransitionConsumed(transition, aborted, finishT);
            mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);
        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
            mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
        } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
+6 −0
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ 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 android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -3686,6 +3687,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
                        getTransitionController(), mWindowManager.mSyncEngine)
                : null;

        if (r.getTaskFragment() != null && r.getTaskFragment().isEmbeddedWithBoundsOverride()
                && transition != null) {
            transition.addFlag(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY);
        }

        final Runnable enterPipRunnable = () -> {
            synchronized (mGlobalLock) {
                if (r.getParent() == null) {
+13 −3
Original line number Diff line number Diff line
@@ -36,7 +36,8 @@ import org.junit.runners.Parameterized
/**
 * Test launching a secondary Activity into Picture-In-Picture mode.
 *
 * Setup: Start from a split A|B. Transition: B enters PIP, observe the window shrink to the bottom
 * Setup: Start from a split A|B.
 * Transition: B enters PIP, observe the window first goes fullscreen then shrink to the bottom
 * right corner on screen.
 *
 * To run this test: `atest FlickerTestsOther:SecondaryActivityEnterPipTest`
@@ -63,7 +64,16 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :
        }
    }

    /** Main and secondary activity start from a split each taking half of the screen. */
    /**
     * We expect the background layer to be visible during this transition.
     */
    @Presubmit
    @Test
    override fun backgroundLayerNeverVisible(): Unit {}

    /**
     * Main and secondary activity start from a split each taking half of the screen.
     */
    @Presubmit
    @Test
    fun layersStartFromEqualSplit() {
@@ -109,7 +119,7 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :
                .isVisible(TRANSITION_SNAPSHOT)
                .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
                .then()
                .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
                .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT, isOptional = true)
        }
        flicker.assertLayersEnd {
            visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)