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

Commit a2ec91ad authored by Winson Chung's avatar Winson Chung Committed by Android (Google) Code Review
Browse files

Merge "Enable launching new tasks from a bubbled task into another bubbled task" into main

parents 4db37c84 927aef1e
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -390,6 +391,7 @@ public class BubbleController implements ConfigurationChangeListener,
        mSyncQueue = syncQueue;
        mWmService = wmService;
        mBubbleTransitions = bubbleTransitions;
        mBubbleTransitions.setBubbleController(this);
        mBubbleTaskViewFactory = new BubbleTaskViewFactory() {
            @Override
            public BubbleTaskView create() {
@@ -1639,6 +1641,39 @@ public class BubbleController implements ConfigurationChangeListener,
        }
    }

    /**
     * Expands and selects a bubble created from a running task in a different mode.
     *
     * @param taskInfo the task.
     */
    @Nullable
    public Transitions.TransitionHandler expandStackAndSelectBubbleForExistingTransition(
            @NonNull ActivityManager.RunningTaskInfo taskInfo,
            @NonNull IBinder transition,
            Consumer<Transitions.TransitionHandler> onInflatedCallback) {
        if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) return null;
        // If there is an existing bubble then just show it
        final String taskKey = Bubble.getAppBubbleKeyForTask(taskInfo);
        if (mBubbleData.hasAnyBubbleWithKey(taskKey)) {
            ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubbleForExistingTransition(): "
                    + "skipping due to existing bubbled task=%d", taskInfo.taskId);
            return null;
        }

        // Otherwise, create a new bubble and show it
        Bubble b = mBubbleData.getOrCreateBubble(taskInfo); // Removes from overflow
        ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubbleForExistingTransition() taskId=%s",
                taskInfo.taskId);
        b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);

        // Lazy init stack view when a bubble is created
        ensureBubbleViewsAndWindowCreated();
        return mBubbleTransitions.startLaunchNewTaskBubbleForExistingTransition(b,
                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.
+9 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.TaskStackListenerCallback
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES_NOISY
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper
import com.android.wm.shell.taskview.TaskViewTaskController

@@ -50,6 +51,10 @@ class BubbleTaskStackListener(
        clearedTask: Boolean,
        wasVisible: Boolean,
    ) {
        ProtoLog.d(
            WM_SHELL_BUBBLES_NOISY,
            "BubbleTaskStackListener.onActivityRestartAttempt(): taskId=%d",
            task.taskId)
        val taskId = task.taskId
        bubbleData.getBubbleInStackWithTaskId(taskId)?.let { bubble ->
            if (isBubbleToFullscreen(task)) {
@@ -133,6 +138,10 @@ class BubbleTaskStackListener(
        bubble: Bubble,
        task: ActivityManager.RunningTaskInfo,
    ) {
        ProtoLog.d(
            WM_SHELL_BUBBLES_NOISY,
            "BubbleTaskStackListener.collapsedBubbleToFullscreenInternal(): taskId=%d",
            task.taskId)
        val taskViewTaskController: TaskViewTaskController = bubble.taskView.controller
        val taskOrganizer: ShellTaskOrganizer = taskViewTaskController.taskOrganizer

+301 −12
Original line number Diff line number Diff line
@@ -78,6 +78,7 @@ import com.android.wm.shell.transition.Transitions.TransitionHandler;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

/**
 * Implements transition coordination for bubble operations.
@@ -107,6 +108,8 @@ public class BubbleTransitions {
    private final Map<IBinder, TransitionHandler> mEnterTransitions =
            new HashMap<>();

    private BubbleController mBubbleController;

    public BubbleTransitions(Context context,
            @NonNull Transitions transitions, @NonNull ShellTaskOrganizer organizer,
            @NonNull TaskViewRepository repository, @NonNull BubbleData bubbleData,
@@ -120,6 +123,10 @@ public class BubbleTransitions {
        mContext = context;
    }

    void setBubbleController(BubbleController controller) {
        mBubbleController = controller;
    }

    /**
     * Returns whether there is a pending transition for the given request.
     */
@@ -181,6 +188,37 @@ public class BubbleTransitions {
                stackView, layerView, iconFactory, inflateSync);
    }

    /**
     * Called to initiate axed bubble-to-bubble launch/convert for the given transition.
     *
     * @return whether a new transition was started for the launch
     */
    public boolean startBubbleToBubbleLaunch(@NonNull IBinder transition,
            @NonNull ActivityManager.RunningTaskInfo launchingTask,
            @NonNull Consumer<TransitionHandler> onInflatedCallback) {
        TransitionHandler handler =
                mBubbleController.expandStackAndSelectBubbleForExistingTransition(
                        launchingTask, transition, onInflatedCallback);
        if (handler != null) {
            mEnterTransitions.put(transition, handler);
        }
        return handler != null;
    }

    /**
     * Starts a new launch or convert transition to show the given bubble.
     */
    public TransitionHandler startLaunchNewTaskBubbleForExistingTransition(Bubble bubble,
            BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory,
            BubblePositioner positioner, BubbleStackView stackView,
            BubbleBarLayerView layerView, BubbleIconFactory iconFactory,
            boolean inflateSync, IBinder transition,
            Consumer<TransitionHandler> onInflatedCallback) {
        return new LaunchNewTaskBubbleForExistingTransition(bubble, mContext, expandedViewManager,
                factory, positioner, stackView, layerView, iconFactory, inflateSync, transition,
                onInflatedCallback);
    }

    /**
     * Starts a convert-to-bubble transition.
     *
@@ -252,7 +290,7 @@ public class BubbleTransitions {
    }

    /**
     * Information about the task when it is being dragged to a bubble
     * Information about the task when it is being dragged to a bubble.
     */
    public static class DragData {
        private final boolean mReleasedOnLeft;
@@ -304,12 +342,14 @@ public class BubbleTransitions {
    }

    /**
     * Keeps track of internal state of different steps of a BubbleTransition.
     * Keeps track of internal state of different steps of a BubbleTransition. Serves as a gating
     * mechanism to block animations or updates until necessary states are set.
     */
    private static class TransitionProgress {

        private final Bubble mBubble;
        private boolean mTransitionReady;
        private boolean mInflated;
        private boolean mReadyToExpand;
        private boolean mSurfaceReady;

@@ -317,34 +357,287 @@ public class BubbleTransitions {
            mBubble = bubble;
        }

        void setInflated() {
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "TransitionProgress.setInflated()");
            mInflated = true;
            onUpdate();
        }

        void setTransitionReady() {
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "TransitionProgress.setTransitionReady()");
            mTransitionReady = true;
            onUpdate();
        }

        void setReadyToExpand() {
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "TransitionProgress.setReadyToExpand()");
            mReadyToExpand = true;
            onUpdate();
        }

        void setSurfaceReady() {
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "TransitionProgress.setSurfaceReady()");
            mSurfaceReady = true;
            onUpdate();
        }

        boolean isReadyToAnimate() {
            // Animation only depends on transition and surface state
            return mTransitionReady && mSurfaceReady;
            return mTransitionReady && mSurfaceReady && mInflated;
        }

        private void onUpdate() {
            if (mTransitionReady && mReadyToExpand && mSurfaceReady) {
            if (mTransitionReady && mReadyToExpand && mSurfaceReady && mInflated) {
                // Clear the transition from bubble when all the steps are ready
                mBubble.setPreparingTransition(null);
            }
        }
    }

    /**
     * Starts a new bubble for an existing playing transition.
     * TODO(b/408328557): To be consolidated with LaunchOrConvertToBubble and ConvertToBubble
     */
    @VisibleForTesting
    class LaunchNewTaskBubbleForExistingTransition implements TransitionHandler, BubbleTransition {
        final BubbleBarLayerView mLayerView;
        private final TransitionProgress mTransitionProgress;
        Bubble mBubble;
        IBinder mTransition;
        Transitions.TransitionFinishCallback mFinishCb;
        WindowContainerTransaction mFinishWct = null;
        final Rect mStartBounds = new Rect();
        SurfaceControl mSnapshot = null;
        // The task info is resolved once we find the task from the transition info using the
        // pending launch cookie otherwise
        @Nullable
        TaskInfo mTaskInfo;
        BubbleViewProvider mPriorBubble = null;
        // Whether we should play the convert-task animation, or the launch-task animation
        private boolean mPlayConvertTaskAnimation;

        private SurfaceControl.Transaction mFinishT;
        private SurfaceControl mTaskLeash;

        LaunchNewTaskBubbleForExistingTransition(Bubble bubble, Context context,
                BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory factory,
                BubblePositioner positioner, BubbleStackView stackView,
                BubbleBarLayerView layerView, BubbleIconFactory iconFactory,
                boolean inflateSync, IBinder transition,
                Consumer<TransitionHandler> onInflatedCallback) {
            mBubble = bubble;
            mTransition = transition;
            mTransitionProgress = new TransitionProgress(bubble);
            mLayerView = layerView;
            mBubble.setInflateSynchronously(inflateSync);
            mBubble.setPreparingTransition(this);
            mBubble.inflate(
                    b -> {
                        onInflated(b);
                        onInflatedCallback.accept(LaunchNewTaskBubbleForExistingTransition.this);
                    },
                    context,
                    expandedViewManager,
                    factory,
                    positioner,
                    stackView,
                    layerView,
                    iconFactory,
                    false /* skipInflation */);
        }

        @VisibleForTesting
        void onInflated(Bubble b) {
            if (b != mBubble) {
                throw new IllegalArgumentException("inflate callback doesn't match bubble");
            }
            if (!mBubble.isShortcut() && !mBubble.isApp()) {
                throw new IllegalArgumentException("Unsupported bubble type");
            }
            final Rect launchBounds = new Rect();
            mLayerView.getExpandedViewRestBounds(launchBounds);

            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();
            mTaskViewTransitions.enqueueExternal(tv.getController(), () -> {
                return mTransition;
            });
        }

        @Override
        public void skip() {
            mBubble.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;
        }

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

            // Identify the task that we are converting or launching. Note, we iterate back to front
            // so that we can adjust alpha for revealed surfaces as needed.
            boolean found = false;
            mPlayConvertTaskAnimation = false;
            for (int i = info.getChanges().size() - 1; i >= 0; i--) {
                final TransitionInfo.Change chg = info.getChanges().get(i);
                final boolean isTaskToConvertToBubble = (chg.getTaskInfo() != null)
                        && (chg.getMode() == TRANSIT_CHANGE || isOpeningMode(chg.getMode()));
                if (isTaskToConvertToBubble) {
                    mStartBounds.set(chg.getStartAbsBounds());
                    // Converting a task into taskview, so treat as "new"
                    mFinishWct = new WindowContainerTransaction();
                    mTaskInfo = chg.getTaskInfo();
                    mFinishT = finishTransaction;
                    mTaskLeash = chg.getLeash();
                    mSnapshot = chg.getSnapshot();
                    // TODO: This should be set for the CHANGE transition, but for some reason there
                    //  is no snapshot, so fallback to the open transition for now
                    mPlayConvertTaskAnimation = false;
                    found = true;
                } else {
                    // In core-initiated launches, the transition is of an OPEN type, and we need to
                    // manually show the surfaces behind the newly bubbled task
                    if (info.getType() == TRANSIT_OPEN && isOpeningMode(chg.getMode())) {
                        startTransaction.setAlpha(chg.getLeash(), 1f);
                    }
                }
            }
            if (!found) {
                Slog.w(TAG, "Expected a TaskView conversion in this transition but didn't get "
                        + "one, cleaning up the task view");
                mBubble.getTaskView().getController().setTaskNotFound();
                mTaskViewTransitions.onExternalDone(mTransition);
                return false;
            }
            mFinishCb = finishCallback;

            // Now update state (and talk to launcher) in parallel with snapshot stuff
            mBubbleData.notificationEntryUpdated(mBubble, /* suppressFlyout= */ true,
                    /* showInShade= */ false);

            if (mPlayConvertTaskAnimation) {
                final int left = mStartBounds.left - info.getRoot(0).getOffset().x;
                final int top = mStartBounds.top - info.getRoot(0).getOffset().y;
                startTransaction.setPosition(mTaskLeash, left, top);
                startTransaction.show(mSnapshot);
                // Move snapshot to root so that it remains visible while task is moved to taskview
                startTransaction.reparent(mSnapshot, info.getRoot(0).getLeash());
                startTransaction.setPosition(mSnapshot, left, top);
                startTransaction.setLayer(mSnapshot, Integer.MAX_VALUE);
            } else {
                final int left = mStartBounds.left - info.getRoot(0).getOffset().x;
                final int top = mStartBounds.top - info.getRoot(0).getOffset().y;
                startTransaction.setPosition(mTaskLeash, left, top);
            }
            startTransaction.apply();

            mTaskViewTransitions.onExternalDone(mTransition);
            mTransitionProgress.setTransitionReady();
            startExpandAnim();
            return true;
        }

        private void startExpandAnim() {
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubbleTransitions.startExpandAnim(): "
                    + "readyToAnimate=%b", mTransitionProgress.isReadyToAnimate());
            if (mLayerView.canExpandView(mBubble)) {
                mPriorBubble = mLayerView.prepareConvertedView(mBubble);
            } else if (mLayerView.isExpanded()) {
                mTransitionProgress.setReadyToExpand();
            }
            if (mTransitionProgress.isReadyToAnimate()) {
                playAnimation();
            }
        }

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

        @Override
        public void surfaceCreated() {
            mTransitionProgress.setSurfaceReady();
            mMainExecutor.execute(() -> {
                ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubbleTransitions.surfaceCreated(): "
                        + "mTaskLeash=%s", mTaskLeash);
                final TaskViewTaskController tvc = mBubble.getTaskView().getController();
                final TaskViewRepository.TaskViewState state = mRepository.byTaskView(tvc);
                if (state == null) return;
                state.mVisible = true;
                if (mTransitionProgress.isReadyToAnimate()) {
                    playAnimation();
                }
            });
        }

        private void playAnimation() {
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubbleTransitions.playAnimation()");
            final TaskViewTaskController tv = mBubble.getTaskView().getController();
            final SurfaceControl.Transaction startT = new SurfaceControl.Transaction();
            // Set task position to 0,0 as it will be placed inside the TaskView
            startT.setPosition(mTaskLeash, 0, 0)
                    .reparent(mTaskLeash, mBubble.getTaskView().getSurfaceControl())
                    .setAlpha(mTaskLeash, 1f)
                    .show(mTaskLeash);
            mTaskViewTransitions.prepareOpenAnimation(tv, true /* new */, startT, mFinishT,
                    (ActivityManager.RunningTaskInfo) mTaskInfo, mTaskLeash, mFinishWct);
            // 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
            mTaskOrganizer.addListenerForTaskId(tv, mTaskInfo.taskId);

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

            float startScale = 1f;
            if (mPlayConvertTaskAnimation) {
                mLayerView.animateConvert(startT, mStartBounds, startScale, mSnapshot,
                        mTaskLeash,
                        this::cleanup);
            } else {
                startT.apply();
                mLayerView.animateExpand(null, this::cleanup);
            }
        }

        private void cleanup() {
            mFinishCb.onTransitionFinished(mFinishWct);
            mFinishCb = 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).
@@ -369,7 +662,6 @@ public class BubbleTransitions {
        BubbleViewProvider mPriorBubble = null;
        // Whether we should play the convert-task animation, or the launch-task animation
        private boolean mPlayConvertTaskAnimation;
        private boolean mPlayingAnimation;

        private SurfaceControl.Transaction mFinishT;
        private SurfaceControl mTaskLeash;
@@ -414,6 +706,7 @@ public class BubbleTransitions {
            if (state != null) {
                state.mVisible = true;
            }
            mTransitionProgress.setInflated();
            mTaskViewTransitions.enqueueExternal(tv.getController(), () -> {
                // We need to convert the next launch into a bubble
                mLaunchCookie = new ActivityOptions.LaunchCookie();
@@ -596,11 +889,11 @@ public class BubbleTransitions {
        public void surfaceCreated() {
            mTransitionProgress.setSurfaceReady();
            mMainExecutor.execute(() -> {
                ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubbleTransitions.surfaceCreated(): "
                        + "mTaskLeash=%s", mTaskLeash);
                final TaskViewTaskController tvc = mBubble.getTaskView().getController();
                final TaskViewRepository.TaskViewState state = mRepository.byTaskView(tvc);
                if (state == null) return;
                ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubbleTransitions.surfaceCreated(): "
                                + "mTaskLeash=%s", mTaskLeash);
                state.mVisible = true;
                if (mTransitionProgress.isReadyToAnimate()) {
                    playAnimation(true /* animate */);
@@ -609,10 +902,6 @@ public class BubbleTransitions {
        }

        private void playAnimation(boolean animate) {
            if (mPlayingAnimation) {
                // Already playing
                return;
            }
            ProtoLog.d(WM_SHELL_BUBBLES_NOISY, "BubbleTransitions.playAnimation(): animate=%b",
                    animate);
            final TaskViewTaskController tv = mBubble.getTaskView().getController();
@@ -648,7 +937,6 @@ public class BubbleTransitions {
                startT.apply();
                cleanup();
            }
            mPlayingAnimation = true;
        }

        private void cleanup() {
@@ -758,6 +1046,7 @@ public class BubbleTransitions {
            if (state != null) {
                state.mVisible = true;
            }
            mTransitionProgress.setInflated();
            mTaskViewTransitions.enqueueExternal(tv.getController(), () -> {
                mTransition = mTransitions.startTransition(TRANSIT_CONVERT_TO_BUBBLE, wct, this);
                return mTransition;
+1 −3
Original line number Diff line number Diff line
@@ -631,7 +631,6 @@ public abstract class WMShellModule {
            Optional<UnfoldTransitionHandler> unfoldHandler,
            Optional<ActivityEmbeddingController> activityEmbeddingController,
            BubbleTransitions bubbleTransitions,
            TaskViewTransitions taskViewTransitions,
            Transitions transitions) {
        return new DefaultMixedHandler(
                shellInit,
@@ -643,8 +642,7 @@ public abstract class WMShellModule {
                desktopTasksController,
                unfoldHandler,
                activityEmbeddingController,
                bubbleTransitions,
                taskViewTransitions);
                bubbleTransitions);
    }

    @WMSingleton
+36 −12

File changed.

Preview size limit exceeded, changes collapsed.

Loading