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

Commit 9f89e861 authored by Ats Jenk's avatar Ats Jenk
Browse files

Start to bubble transition when releasing task in bubble area

When released in bubble area, signal BubbleController to start the
transition to convert the running task into a bubble.
Keep the desktop drag transition running during this.
Once the bubble transition starts, we will first attempte to merge the
animation with the running drag to desktop animation.
Detect this case in the desktop handler and cancel the ongoing drag to
desktop transition.
This will allow bubble transition handler to take over the animation and
move the task into a bubble.

Bug: 388851898
Test: atest BubbleTransitionsTestTest
Test: atest DragToDesktopTransitionHandlerTest
Test: manual, drag a fullscreen task from handle to corner, check it
  converts to bubble on release
Flag: com.android.wm.shell.enable_bubble_to_fullscreen
Change-Id: Ic2b1cd096b89afdde51429f70dafea47bdd45442
parent d9e9dbf6
Loading
Loading
Loading
Loading
+9 −2
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.service.notification.NotificationListenerService.REASON_CA
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.TRANSIT_CHANGE;

import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -1590,20 +1591,26 @@ public class BubbleController implements ConfigurationChangeListener,
     * Expands and selects a bubble created from a running task in a different mode.
     *
     * @param taskInfo the task.
     * @param dragData optional information about the task when it is being dragged into a bubble
     */
    public void expandStackAndSelectBubble(ActivityManager.RunningTaskInfo taskInfo) {
    public void expandStackAndSelectBubble(ActivityManager.RunningTaskInfo taskInfo,
            @Nullable BubbleTransitions.DragData dragData) {
        if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) return;
        Bubble b = mBubbleData.getOrCreateBubble(taskInfo); // Removes from overflow
        ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", taskInfo.taskId);
        if (b.isInflated()) {
            mBubbleData.setSelectedBubbleAndExpandStack(b);
            if (dragData != null && dragData.getPendingWct() != null) {
                mTransitions.startTransition(TRANSIT_CHANGE,
                        dragData.getPendingWct(), /* handler= */ null);
            }
        } else {
            b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
            // Lazy init stack view when a bubble is created
            ensureBubbleViewsAndWindowCreated();
            mBubbleTransitions.startConvertToBubble(b, taskInfo, mExpandedViewManager,
                    mBubbleTaskViewFactory, mBubblePositioner, mStackView, mLayerView,
                    mBubbleIconFactory, mInflateSynchronously);
                    mBubbleIconFactory, dragData, mInflateSynchronously);
        }
    }

+54 −3
Original line number Diff line number Diff line
@@ -92,10 +92,10 @@ public class BubbleTransitions {
            BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory,
            BubblePositioner positioner, BubbleStackView stackView,
            BubbleBarLayerView layerView, BubbleIconFactory iconFactory,
            boolean inflateSync) {
            DragData dragData, boolean inflateSync) {
        return new ConvertToBubble(bubble, taskInfo, mContext,
                expandedViewManager, factory, positioner, stackView, layerView, iconFactory,
                inflateSync);
                dragData, inflateSync);
    }

    /**
@@ -148,6 +148,39 @@ public class BubbleTransitions {
        default void continueCollapse() {}
    }

    /**
     * Information about the task when it is being dragged to a bubble
     */
    public static class DragData {
        private final Rect mBounds;
        private final WindowContainerTransaction mPendingWct;

        /**
         * @param bounds bounds of the dragged task when the drag was released
         * @param wct pending operations to be applied when finishing the drag
         */
        public DragData(@Nullable Rect bounds, @Nullable WindowContainerTransaction wct) {
            mBounds = bounds;
            mPendingWct = wct;
        }

        /**
         * @return bounds of the dragged task when the drag was released
         */
        @Nullable
        public Rect getBounds() {
            return mBounds;
        }

        /**
         * @return pending operations to be applied when finishing the drag
         */
        @Nullable
        public WindowContainerTransaction getPendingWct() {
            return mPendingWct;
        }
    }

    /**
     * BubbleTransition that coordinates the process of a non-bubble task becoming a bubble. The
     * steps are as follows:
@@ -167,6 +200,7 @@ public class BubbleTransitions {
    class ConvertToBubble implements Transitions.TransitionHandler, BubbleTransition {
        final BubbleBarLayerView mLayerView;
        Bubble mBubble;
        @Nullable DragData mDragData;
        IBinder mTransition;
        Transitions.TransitionFinishCallback mFinishCb;
        WindowContainerTransaction mFinishWct = null;
@@ -182,10 +216,12 @@ public class BubbleTransitions {
        ConvertToBubble(Bubble bubble, TaskInfo taskInfo, Context context,
                BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory,
                BubblePositioner positioner, BubbleStackView stackView,
                BubbleBarLayerView layerView, BubbleIconFactory iconFactory, boolean inflateSync) {
                BubbleBarLayerView layerView, BubbleIconFactory iconFactory,
                @Nullable DragData dragData, boolean inflateSync) {
            mBubble = bubble;
            mTaskInfo = taskInfo;
            mLayerView = layerView;
            mDragData = dragData;
            mBubble.setInflateSynchronously(inflateSync);
            mBubble.setPreparingTransition(this);
            mBubble.inflate(
@@ -208,6 +244,9 @@ public class BubbleTransitions {
            final Rect launchBounds = new Rect();
            mLayerView.getExpandedViewRestBounds(launchBounds);
            WindowContainerTransaction wct = new WindowContainerTransaction();
            if (mDragData != null && mDragData.getPendingWct() != null) {
                wct.merge(mDragData.getPendingWct(), true);
            }
            if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
                if (mTaskInfo.getParentTaskId() != INVALID_TASK_ID) {
                    wct.reparent(mTaskInfo.token, null, true);
@@ -292,6 +331,11 @@ public class BubbleTransitions {
            }
            mFinishCb = finishCallback;

            if (mDragData != null && mDragData.getBounds() != null) {
                // Override start bounds with the dragged task bounds
                mStartBounds.set(mDragData.getBounds());
            }

            // Now update state (and talk to launcher) in parallel with snapshot stuff
            mBubbleData.notificationEntryUpdated(mBubble, /* suppressFlyout= */ true,
                    /* showInShade= */ false);
@@ -303,6 +347,13 @@ public class BubbleTransitions {
                    mStartBounds.left - info.getRoot(0).getOffset().x,
                    mStartBounds.top - info.getRoot(0).getOffset().y);
            startTransaction.setLayer(mSnapshot, Integer.MAX_VALUE);

            BubbleBarExpandedView bbev = mBubble.getBubbleBarExpandedView();
            if (bbev != null) {
                // Corners get reset during the animation. Add them back
                startTransaction.setCornerRadius(mSnapshot, bbev.getRestingCornerRadius());
            }

            startTransaction.apply();

            mTaskViewTransitions.onExternalDone(transition);
+4 −3
Original line number Diff line number Diff line
@@ -915,14 +915,15 @@ public abstract class WMShellModule {
            Transitions transitions,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            @DynamicOverride DesktopUserRepositories desktopUserRepositories,
            InteractionJankMonitor interactionJankMonitor) {
            InteractionJankMonitor interactionJankMonitor,
            Optional<BubbleController> bubbleController) {
        return ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX.isTrue()
                ? new SpringDragToDesktopTransitionHandler(
                context, transitions, rootTaskDisplayAreaOrganizer, desktopUserRepositories,
                interactionJankMonitor)
                interactionJankMonitor, bubbleController)
                : new DefaultDragToDesktopTransitionHandler(
                        context, transitions, rootTaskDisplayAreaOrganizer, desktopUserRepositories,
                        interactionJankMonitor);
                        interactionJankMonitor, bubbleController);
    }

    @WMSingleton
+13 −5
Original line number Diff line number Diff line
@@ -2689,15 +2689,22 @@ class DesktopTasksController(
    }

    /** Requests a task be transitioned from whatever mode it's in to a bubble. */
    fun requestFloat(taskInfo: RunningTaskInfo) {
    @JvmOverloads
    fun requestFloat(taskInfo: RunningTaskInfo, left: Boolean? = null) {
        val isDragging = dragToDesktopTransitionHandler.inProgress
        val shouldRequestFloat =
            taskInfo.isFullscreen || taskInfo.isFreeform || isDragging || taskInfo.isMultiWindow
        if (!shouldRequestFloat) return
        if (isDragging) {
            releaseVisualIndicator()
            val cancelState =
                if (left == true) DragToDesktopTransitionHandler.CancelState.CANCEL_BUBBLE_LEFT
                else DragToDesktopTransitionHandler.CancelState.CANCEL_BUBBLE_RIGHT
            dragToDesktopTransitionHandler.cancelDragToDesktopTransition(cancelState)
        } else {
            bubbleController.ifPresent { it.expandStackAndSelectBubble(taskInfo) }
            bubbleController.ifPresent {
                it.expandStackAndSelectBubble(taskInfo, /* dragData= */ null)
            }
        }
    }

@@ -2975,10 +2982,11 @@ class DesktopTasksController(
                )
                requestSplit(taskInfo, leftOrTop = false)
            }
            IndicatorType.TO_BUBBLE_LEFT_INDICATOR,
            IndicatorType.TO_BUBBLE_LEFT_INDICATOR -> {
                requestFloat(taskInfo, left = true)
            }
            IndicatorType.TO_BUBBLE_RIGHT_INDICATOR -> {
                // TODO(b/388851898): move to bubble
                cancelDragToDesktop(taskInfo)
                requestFloat(taskInfo, left = false)
            }
        }
        return indicatorType
+81 −8
Original line number Diff line number Diff line
@@ -37,6 +37,8 @@ import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.animation.FloatProperties
import com.android.wm.shell.bubbles.BubbleController
import com.android.wm.shell.bubbles.BubbleTransitions
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
@@ -53,6 +55,7 @@ import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.util.Optional
import java.util.function.Supplier
import kotlin.math.max

@@ -72,6 +75,7 @@ sealed class DragToDesktopTransitionHandler(
    private val taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
    private val desktopUserRepositories: DesktopUserRepositories,
    protected val interactionJankMonitor: InteractionJankMonitor,
    private val bubbleController: Optional<BubbleController>,
    protected val transactionSupplier: Supplier<SurfaceControl.Transaction>,
) : TransitionHandler {

@@ -241,6 +245,20 @@ sealed class DragToDesktopTransitionHandler(
            state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null)
            requestSplitFromScaledTask(splitPosition, wct)
            clearState()
        } else if (
            state.draggedTaskChange != null &&
                (cancelState == CancelState.CANCEL_BUBBLE_LEFT ||
                    cancelState == CancelState.CANCEL_BUBBLE_RIGHT)
        ) {
            if (!bubbleController.isPresent) {
                startCancelAnimation()
            } else {
                // Animation is handled by BubbleController
                val wct = WindowContainerTransaction()
                restoreWindowOrder(wct, state)
                // TODO(b/388851898): pass along information about left or right side
                requestBubbleFromScaledTask(wct)
            }
        } else {
            // There's no dragged task, this can happen when the "cancel" happened too quickly
            // before the "start" transition is even ready (like on a fling gesture). The
@@ -256,6 +274,13 @@ sealed class DragToDesktopTransitionHandler(
        @SplitPosition splitPosition: Int,
        wct: WindowContainerTransaction,
    ) {
        val state = requireTransitionState()
        val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo")
        val animatedTaskBounds = getAnimatedTaskBounds()
        requestSplitSelect(wct, taskInfo, splitPosition, animatedTaskBounds)
    }

    private fun getAnimatedTaskBounds(): Rect {
        val state = requireTransitionState()
        val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo")
        val taskBounds = Rect(taskInfo.configuration.windowConfiguration.bounds)
@@ -264,14 +289,12 @@ sealed class DragToDesktopTransitionHandler(
        val scaledHeight = taskBounds.height() * taskScale
        val dragPosition = PointF(state.dragAnimator.position)
        state.dragAnimator.cancelAnimator()
        val animatedTaskBounds =
            Rect(
        return Rect(
            dragPosition.x.toInt(),
            dragPosition.y.toInt(),
            (dragPosition.x + scaledWidth).toInt(),
            (dragPosition.y + scaledHeight).toInt(),
        )
        requestSplitSelect(wct, taskInfo, splitPosition, animatedTaskBounds)
    }

    private fun requestSplitSelect(
@@ -294,6 +317,25 @@ sealed class DragToDesktopTransitionHandler(
        splitScreenController.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds)
    }

    private fun requestBubbleFromScaledTask(wct: WindowContainerTransaction) {
        // TODO(b/391928049): update density once we can drag from desktop to bubble
        val state = requireTransitionState()
        val taskInfo = state.draggedTaskChange?.taskInfo ?: error("Expected non-null taskInfo")
        val taskBounds = getAnimatedTaskBounds()
        state.dragAnimator.cancelAnimator()
        requestBubble(wct, taskInfo, taskBounds)
    }

    private fun requestBubble(
        wct: WindowContainerTransaction,
        taskInfo: RunningTaskInfo,
        taskBounds: Rect = Rect(taskInfo.configuration.windowConfiguration.bounds),
    ) {
        val controller =
            bubbleController.orElseThrow { IllegalStateException("BubbleController not set") }
        controller.expandStackAndSelectBubble(taskInfo, BubbleTransitions.DragData(taskBounds, wct))
    }

    override fun startAnimation(
        transition: IBinder,
        info: TransitionInfo,
@@ -446,6 +488,16 @@ sealed class DragToDesktopTransitionHandler(
            state.startTransitionFinishTransaction?.apply()
            state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null)
            requestSplitSelect(wct, taskInfo, splitPosition)
        } else if (
            state.cancelState == CancelState.CANCEL_BUBBLE_LEFT ||
                state.cancelState == CancelState.CANCEL_BUBBLE_RIGHT
        ) {
            val taskInfo =
                state.draggedTaskChange?.taskInfo ?: error("Expected non-null task info.")
            val wct = WindowContainerTransaction()
            restoreWindowOrder(wct)
            // TODO(b/388851898): pass along information about left or right side
            requestBubble(wct, taskInfo)
        }
        return true
    }
@@ -476,6 +528,19 @@ sealed class DragToDesktopTransitionHandler(
            clearState()
            return
        }
        // In case of bubble animation, finish the initial desktop drag animation, but keep the
        // current animation running and have bubbles take over
        if (
            state.cancelState == CancelState.CANCEL_BUBBLE_LEFT ||
                state.cancelState == CancelState.CANCEL_BUBBLE_RIGHT
        ) {
            // TODO(b/388851898): once bubbles sends a specific type of transition for the enter
            //  bubble, add a check for that transition type to ensure that any other transition
            //  would not end up here
            state.startTransitionFinishCb?.onTransitionFinished(/* wct= */ null)
            clearState()
            return
        }
        val isCancelTransition =
            info.type == TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP &&
                transition == state.cancelTransitionToken &&
@@ -869,6 +934,10 @@ sealed class DragToDesktopTransitionHandler(
        CANCEL_SPLIT_LEFT,
        /** A cancel event where the task will request to enter split on the right side. */
        CANCEL_SPLIT_RIGHT,
        /** A cancel event where the task will request to bubble on the left side. */
        CANCEL_BUBBLE_LEFT,
        /** A cancel event where the task will request to bubble on the right side. */
        CANCEL_BUBBLE_RIGHT,
    }

    companion object {
@@ -887,6 +956,7 @@ constructor(
    taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
    desktopUserRepositories: DesktopUserRepositories,
    interactionJankMonitor: InteractionJankMonitor,
    bubbleController: Optional<BubbleController>,
    transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
        SurfaceControl.Transaction()
    },
@@ -897,6 +967,7 @@ constructor(
        taskDisplayAreaOrganizer,
        desktopUserRepositories,
        interactionJankMonitor,
        bubbleController,
        transactionSupplier,
    ) {

@@ -925,6 +996,7 @@ constructor(
    taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
    desktopUserRepositories: DesktopUserRepositories,
    interactionJankMonitor: InteractionJankMonitor,
    bubbleController: Optional<BubbleController>,
    transactionSupplier: Supplier<SurfaceControl.Transaction> = Supplier {
        SurfaceControl.Transaction()
    },
@@ -935,6 +1007,7 @@ constructor(
        taskDisplayAreaOrganizer,
        desktopUserRepositories,
        interactionJankMonitor,
        bubbleController,
        transactionSupplier,
    ) {

Loading