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

Commit c197fc65 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add jumpcut TransitionHandler for Task Trampoline Bubble launch" into main

parents ea686dc6 33a4e555
Loading
Loading
Loading
Loading
+38 −0
Original line number Diff line number Diff line
@@ -1812,6 +1812,44 @@ public class BubbleController implements ConfigurationChangeListener,
                onInflatedCallback);
    }

    /**
     * Jumpcut animation to switch the Task showing in expanded Bubble.
     */
    @NonNull
    Transitions.TransitionHandler jumpcutBubbleSwitchTransition(
            @NonNull ActivityManager.RunningTaskInfo openingTaskInfo,
            @NonNull ActivityManager.RunningTaskInfo closingTaskInfo,
            @NonNull IBinder transition,
            @NonNull Consumer<Transitions.TransitionHandler> onInflatedCallback) {
        final Bubble existingBubble = mBubbleData.getBubbleInStackWithTaskId(
                closingTaskInfo.taskId);
        if (existingBubble == null || mBubbleData.getSelectedBubble() != existingBubble
                || !mBubbleData.isExpanded()) {
            ProtoLog.w(WM_SHELL_BUBBLES, "The existing Bubble for taskId=%s was not expanded,"
                            + " fallback to expandStackAndSelectBubbleForExistingTransition",
                    closingTaskInfo.taskId);
            return expandStackAndSelectBubbleForExistingTransition(openingTaskInfo, transition,
                    onInflatedCallback);
        } else if (mBubbleData.getBubbleInStackWithTaskId(openingTaskInfo.taskId) != null) {
            ProtoLog.w(WM_SHELL_BUBBLES, "There is an existing Bubble for the new launch taskId=%s,"
                            + " fallback to expandStackAndSelectBubbleForExistingTransition",
                    closingTaskInfo.taskId);
            return expandStackAndSelectBubbleForExistingTransition(openingTaskInfo, transition,
                    onInflatedCallback);
        }

        ProtoLog.v(WM_SHELL_BUBBLES, "jumpcutBubbleSwitchTransition() newTaskId=%s oldTaskId=%s",
                openingTaskInfo.taskId, closingTaskInfo.taskId);

        final Bubble newBubble = mBubbleData.getOrCreateBubble(openingTaskInfo);
        newBubble.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);

        return mBubbleTransitions.startJumpcutBubbleSwitchTransition(newBubble, existingBubble,
                mExpandedViewManager, mBubbleTaskViewFactory, mBubblePositioner, mStackView,
                mLayerView, mBubbleIconFactory, mInflateSynchronously, transition,
                onInflatedCallback);
    }

    /**
     * Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble
     * exists for this entry, and it is able to bubble, a new bubble will be created.
+257 −0
Original line number Diff line number Diff line
@@ -245,6 +245,19 @@ public class BubbleTransitions {
        mEnterTransitions.put(transition, handler);
    }

    /**
     * Initiates a Task Trampoline Bubble launch for the given transition.
     */
    public void startTaskTrampolineBubbleLaunch(@NonNull IBinder transition,
            @NonNull ActivityManager.RunningTaskInfo openingTask,
            @NonNull ActivityManager.RunningTaskInfo closingTask,
            @NonNull Consumer<TransitionHandler> onInflatedCallback) {
        final TransitionHandler handler =
                mBubbleController.jumpcutBubbleSwitchTransition(openingTask, closingTask,
                        transition, onInflatedCallback);
        mEnterTransitions.put(transition, handler);
    }

    /**
     * Starts a new launch or convert transition to show the given bubble.
     */
@@ -259,6 +272,19 @@ public class BubbleTransitions {
                onInflatedCallback);
    }

    /**
     * Starts a jumpcut transition to update Task in the expanding Bubble.
     */
    public TransitionHandler startJumpcutBubbleSwitchTransition(Bubble openingBubble,
            Bubble closingBubble, BubbleExpandedViewManager expandedViewManager,
            BubbleTaskViewFactory factory, BubblePositioner positioner, BubbleStackView stackView,
            BubbleBarLayerView layerView, BubbleIconFactory iconFactory, boolean inflateSync,
            IBinder transition, Consumer<TransitionHandler> onInflatedCallback) {
        return new JumpcutBubbleSwitchTransition(openingBubble, closingBubble, mContext,
                expandedViewManager, factory, positioner, stackView, layerView, iconFactory,
                inflateSync, transition, onInflatedCallback);
    }

    /**
     * Starts a convert-to-bubble transition.
     *
@@ -792,6 +818,237 @@ public class BubbleTransitions {
        }
    }

    /**
     * Starts a jumpcut to update Task in the expanding Bubble transition.
     *
     * 1. In transition startTransaction, ensure the closing Task surface is attached to its Bubble
     *    TaskView, so that it is visible and unchanged.
     * 2. When opening Bubble TaskView is ready, ensure the opening Task surface is attached to its
     *    TaskView, and being visible, so that we only need to animate TaskView next.
     * 3. Apply jumpcut for the Bubble switch animation, and apply the Transition finishCallback
     *    after the TaskViews finish surface update.
     * TODO(b/408328557): To be consolidated with LaunchOrConvertToBubble and ConvertToBubble
     */
    @VisibleForTesting
    class JumpcutBubbleSwitchTransition implements TransitionHandler, BubbleTransition {
        final BubbleExpandedViewTransitionAnimator mExpandedViewAnimator;
        private final TransitionProgress mTransitionProgress;
        Bubble mOpeningBubble;
        Bubble mClosingBubble;

        IBinder mTransition;
        Transitions.TransitionFinishCallback mFinishCb;
        WindowContainerTransaction mFinishWct = null;
        // The task info is resolved once we find the task from the transition info using the
        // pending launch cookie otherwise
        @Nullable
        TaskInfo mOpeningTaskInfo;

        private SurfaceControl.Transaction mFinishT;
        private SurfaceControl mTaskLeash;

        JumpcutBubbleSwitchTransition(Bubble openingBubble, Bubble closingBubble, Context context,
                BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory,
                BubblePositioner positioner, BubbleStackView stackView,
                BubbleBarLayerView layerView, BubbleIconFactory iconFactory,
                boolean inflateSync, IBinder transition,
                Consumer<TransitionHandler> onInflatedCallback) {
            if (layerView != null) {
                mExpandedViewAnimator = layerView;
            } else {
                mExpandedViewAnimator = stackView;
            }
            mOpeningBubble = openingBubble;
            mClosingBubble = closingBubble;
            mTransition = transition;
            mTransitionProgress = new TransitionProgress(openingBubble);
            mOpeningBubble.setInflateSynchronously(inflateSync);
            mOpeningBubble.setPreparingTransition(this);
            // Still need the inflate to update the app icon in Bubble.
            mOpeningBubble.inflate(
                    b -> {
                        onInflated(b);
                        onInflatedCallback.accept(JumpcutBubbleSwitchTransition.this);
                    },
                    context,
                    expandedViewManager,
                    factory,
                    positioner,
                    stackView,
                    layerView,
                    iconFactory,
                    mAppInfoProvider,
                    false /* skipInflation */);
        }

        @VisibleForTesting
        void onInflated(Bubble b) {
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "JumpcutBubbleSwitchTransition.onInflated()");
            if (b != mOpeningBubble) {
                throw new IllegalArgumentException("inflate callback doesn't match bubble");
            }
            final TaskView tv = b.getTaskView();
            tv.setSurfaceLifecycle(SurfaceView.SURFACE_LIFECYCLE_FOLLOWS_ATTACHMENT);
            final TaskViewRepository.TaskViewState state = mRepository.byTaskView(
                    tv.getController());
            if (state != null) {
                state.mVisible = true;
            }
            mTransitionProgress.setInflated();
            // Remove any intermediate queued transitions that were started as a result of the
            // inflation (the task view will be in the right bounds)
            mTaskViewTransitions.removePendingTransitions(tv.getController());
            mTaskViewTransitions.enqueueExternal(tv.getController(), () -> mTransition);
        }

        @Override
        public void skip() {
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "JumpcutBubbleSwitchTransition.skip()");
            mOpeningBubble.setPreparingTransition(null);
            cleanup();
        }

        @Override
        public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
                @Nullable TransitionRequestInfo request) {
            return null;
        }

        @Override
        public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
                @NonNull SurfaceControl.Transaction startT,
                @NonNull SurfaceControl.Transaction finishT,
                @NonNull IBinder mergeTarget,
                @NonNull Transitions.TransitionFinishCallback finishCallback) {
        }

        @Override
        public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
                @NonNull SurfaceControl.Transaction finishTransaction) {
            if (!aborted) return;
            mTaskViewTransitions.onExternalDone(mTransition);
            mTransition = null;
        }

        /**
         * @return true As DefaultMixedTransition assumes that this transition will be handled by
         * this handler in all cases.
         */
        @Override
        public boolean startAnimation(@NonNull IBinder transition,
                @NonNull TransitionInfo info,
                @NonNull SurfaceControl.Transaction startTransaction,
                @NonNull SurfaceControl.Transaction finishTransaction,
                @NonNull Transitions.TransitionFinishCallback finishCallback) {
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "JumpcutBubbleSwitchTransition.startAnimation()");

            final TransitionInfo.Change enterBubbleTask = getEnterBubbleTask(info);
            final TransitionInfo.Change closingBubbleTask = getClosingBubbleTask(info);
            mOpeningTaskInfo = enterBubbleTask.getTaskInfo();
            mFinishWct = new WindowContainerTransaction();
            mFinishT = finishTransaction;
            mFinishCb = finishCallback;
            mTaskLeash = enterBubbleTask.getLeash();

            mBubbleData.notificationEntryUpdated(mOpeningBubble, /* suppressFlyout= */ true,
                    /* showInShade= */ false);

            // Keep showing the closing Bubble Task within the closing Bubble TaskView until the
            // opening Bubble TaskView is ready.
            final SurfaceControl closingBubbleTaskLeash = closingBubbleTask.getLeash();
            final SurfaceControl closingBubbleTaskView = mClosingBubble.getTaskView()
                    .getSurfaceControl();
            startTransaction.setAlpha(closingBubbleTaskLeash, 1f)
                    .setPosition(closingBubbleTaskLeash, 0, 0)
                    .reparent(closingBubbleTaskLeash, closingBubbleTaskView)
                    .show(closingBubbleTaskLeash);

            startTransaction.apply();

            mTransitionProgress.setTransitionReady();
            if (mExpandedViewAnimator.canExpandView(mOpeningBubble)) {
                final BubbleViewProvider priorBubble =
                        mExpandedViewAnimator.prepareConvertedView(mOpeningBubble);
                if (priorBubble != mClosingBubble
                        // TODO b/419347947 BubbleStackView will return null for non-overflow bubble
                        && priorBubble != null) {
                    throw new IllegalStateException("Previous expanded Bubble was taskId="
                            + priorBubble.getTaskId() + " but expect taskId="
                            + mClosingBubble.getTaskId());
                }
            } else if (mExpandedViewAnimator.isExpanded()) {
                mTransitionProgress.setReadyToExpand();
            }
            if (mTransitionProgress.isReadyToAnimate()) {
                animateJumpcut();
            }

            return true;
        }

        @Override
        public void continueExpand() {
            mTransitionProgress.setReadyToExpand();
        }

        @Override
        public void surfaceCreated() {
            mTransitionProgress.setSurfaceReady();
            mMainExecutor.execute(() -> {
                ProtoLog.d(WM_SHELL_BUBBLES_NOISY,
                        "JumpcutBubbleSwitchTransition.surfaceCreated()");
                final TaskViewTaskController tvc = mOpeningBubble.getTaskView().getController();
                final TaskViewRepository.TaskViewState state = mRepository.byTaskView(tvc);
                if (state == null) return;
                state.mVisible = true;
                if (mTransitionProgress.isReadyToAnimate()) {
                    animateJumpcut();
                }
            });
        }

        private void animateJumpcut() {
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY,
                    "JumpcutBubbleSwitchTransition.applyJumpcut()");
            mTaskViewTransitions.onExternalDone(mTransition);
            final TaskViewTaskController tv = mOpeningBubble.getTaskView().getController();

            // Prepare the transaction to apply when the TaskView surface is ready.
            final SurfaceControl.Transaction startT = new SurfaceControl.Transaction();
            final SurfaceControl openingTaskViewLeash = mOpeningBubble.getTaskView()
                    .getSurfaceControl();
            startT.setAlpha(mTaskLeash, 1f)
                    // Set task position to 0,0 as it will be placed inside the TaskView
                    .setPosition(mTaskLeash, 0, 0)
                    .reparent(mTaskLeash, openingTaskViewLeash)
                    .show(mTaskLeash);
            mTaskViewTransitions.prepareOpenAnimation(tv, true /* new */, startT, mFinishT,
                    (ActivityManager.RunningTaskInfo) mOpeningTaskInfo, mTaskLeash, mFinishWct);
            startT.apply();

            // Add the task view task listener manually since we aren't going through
            // TaskViewTransitions (which normally sets up the listener via a pending launch cookie)
            // Note: In this path, because a new task is being started, the transition may receive
            // the transition for the task before the organizer does
            mTaskOrganizer.addListenerForTaskId(tv, mOpeningTaskInfo.taskId);

            if (mFinishWct.isEmpty()) {
                mFinishWct = null;
            }

            mExpandedViewAnimator.animateExpand(mClosingBubble, this::cleanup);
        }

        private void cleanup() {
            if (mFinishCb != null) {
                ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "JumpcutBubbleSwitchTransition.cleanup()");
                mFinishCb.onTransitionFinished(mFinishWct);
                mFinishCb = null;
            }
            mOpeningBubble.setPreparingTransition(null);
        }
    }

    /**
     * Starts a new transition into a bubble, which will either play a launch animation (if the task
     * was not previously visible) or a convert animation (if the task is currently visible).
+5 −4
Original line number Diff line number Diff line
@@ -500,13 +500,14 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
                    transition, info, startTransaction, finishTransaction, finishCallback);
        };

        if (closingBubble != null && isOpeningType(enterBubbleTask.getMode())) {
        if (com.android.window.flags.Flags.fixBubbleTrampolineAnimation()
                && closingBubble != null && isOpeningType(enterBubbleTask.getMode())) {
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
                    + "opening bubble from another closing bubbled task");
            // Task Trampoline (Case 5)
            // TODO(b/417848405): Update the trampoline transition to jumpcut.
            bubbleTransitions.startBubbleToBubbleLaunchOrExistingBubbleConvert(
                    transition, enterBubbleTask.getTaskInfo(), onInflatedCallback);
            bubbleTransitions.startTaskTrampolineBubbleLaunch(
                    transition, enterBubbleTask.getTaskInfo(),
                    closingBubble.getTaskInfo(), onInflatedCallback);
        } else {
            // Opening a Bubble Task (Case 3/6)
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+120 −27

File changed.

Preview size limit exceeded, changes collapsed.

+28 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.os.Binder
import android.platform.test.annotations.EnableFlags
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_OPEN
import android.window.IRemoteTransition
import android.window.RemoteTransition
@@ -28,6 +29,7 @@ import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.internal.hidden_from_bootclasspath.com.android.window.flags.Flags.FLAG_FIX_BUBBLE_TRAMPOLINE_ANIMATION
import com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
@@ -267,6 +269,32 @@ class DefaultMixedHandlerTest : ShellTestCase() {
            any(), any(), any())
    }

    @Test
    @EnableFlags(FLAG_ENABLE_CREATE_ANY_BUBBLE, FLAG_FIX_BUBBLE_TRAMPOLINE_ANIMATION)
    fun test_startAnimation_taskTrampolineBubbleLaunch() {
        val openingChange =
            TransitionInfo.Change(mock<WindowContainerToken>(), mock<SurfaceControl>())
        openingChange.mode = TRANSIT_OPEN
        openingChange.taskInfo = createRunningTask()
        val closingChange =
            TransitionInfo.Change(mock<WindowContainerToken>(), mock<SurfaceControl>())
        closingChange.mode = TRANSIT_CLOSE
        closingChange.taskInfo = createRunningTask()
        val info = TransitionInfo(TRANSIT_OPEN, 0)
        info.addChange(openingChange)
        info.addChange(closingChange)

        bubbleController.stub {
            on { shouldBeAppBubble(any()) } doReturn true
        }

        mixedHandler.startAnimation(Binder(), info, mock<SurfaceControl.Transaction>(),
            mock<SurfaceControl.Transaction>(), mock<Transitions.TransitionFinishCallback>())

        verify(bubbleTransitions).startTaskTrampolineBubbleLaunch(
            any(), eq(openingChange.taskInfo!!), eq(closingChange.taskInfo!!), any())
    }

    private fun createTransitionRequestInfo(
        runningTask: RunningTaskInfo? = null,
        remote: RemoteTransition? = null,