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

Commit be2ec04c authored by Chris Li's avatar Chris Li Committed by Android (Google) Code Review
Browse files

Merge "Distinguish Task Tramplone Bubble launch" into main

parents 286b1467 9ce16142
Loading
Loading
Loading
Loading
+82 −15
Original line number Diff line number Diff line
@@ -20,9 +20,11 @@ import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_A
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.View.INVISIBLE;
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_FRONT;

@@ -132,7 +134,8 @@ public class BubbleTransitions {
        mAppInfoProvider = appInfoProvider;
    }

    void setBubbleController(BubbleController controller) {
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public void setBubbleController(BubbleController controller) {
        mBubbleController = controller;
    }

@@ -143,20 +146,6 @@ public class BubbleTransitions {
        return mBubbleController.shouldBeAppBubble(taskInfo);
    }

    /**
     * Returns whether bubbles are showing as the bubble bar.
     */
    public boolean isShowingAsBubbleBar() {
        return mBubbleController.isShowingAsBubbleBar();
    }

    /**
     * Returns whether there is an existing bubble with the given task id.
     */
    public boolean hasBubbleWithTaskId(int taskId) {
        return mBubbleData.getBubbleInStackWithTaskId(taskId) != null;
    }

    /**
     * Returns whether there is a pending transition for the given request.
     */
@@ -306,6 +295,84 @@ public class BubbleTransitions {
        return new DraggedBubbleIconToFullscreen(bubble, dropLocation);
    }

    /**
     * Finds the Task that is entering Bubble. This can be either a Bubble Task that is becoming
     * visible, or a visible Task that is changing to Bubble from other windowing mode.
     */
    @Nullable
    public TransitionInfo.Change getEnterBubbleTask(@NonNull TransitionInfo info) {
        for (int i = 0; i < info.getChanges().size(); i++) {
            final TransitionInfo.Change chg = info.getChanges().get(i);
            final ActivityManager.RunningTaskInfo taskInfo = chg.getTaskInfo();
            // Exclude activity transition scenarios.
            if (taskInfo == null || taskInfo.getActivityType() != ACTIVITY_TYPE_STANDARD) {
                continue;
            }
            // Only process opening or change transitions.
            if (!isOpeningMode(chg.getMode()) && chg.getMode() != TRANSIT_CHANGE) {
                continue;
            }
            // Skip non-app-bubble tasks (e.g. a reused task in a bubble-to-fullscreen scenario).
            if (!shouldBeAppBubble(taskInfo)) {
                continue;
            }
            return chg;
        }
        return null;
    }

    /**
     * Finds the Bubble Task that is closing.
     * Note: this doesn't find move-to-back Task.
     */
    @Nullable
    public TransitionInfo.Change getClosingBubbleTask(@NonNull TransitionInfo info) {
        for (int i = 0; i < info.getChanges().size(); i++) {
            final TransitionInfo.Change chg = info.getChanges().get(i);
            final ActivityManager.RunningTaskInfo taskInfo = chg.getTaskInfo();
            // Exclude activity transition scenarios.
            if (taskInfo == null || taskInfo.getActivityType() != ACTIVITY_TYPE_STANDARD) {
                continue;
            }
            // Only process closing transitions.
            if (chg.getMode() != TRANSIT_CLOSE) {
                continue;
            }
            // Skip non-app-bubble tasks (e.g., a reused task in a bubble-to-fullscreen scenario).
            if (!shouldBeAppBubble(taskInfo)) {
                continue;
            }
            return chg;
        }
        return null;
    }

    /**
     * Whether the transition contains any Task that is changed from expanded App Bubbled to
     * non-Bubbled.
     */
    public boolean containsNonBubbledExpandedTaskInStack(@NonNull TransitionInfo info) {
        if (!mBubbleData.isExpanded() || mBubbleData.getSelectedBubble() == null) {
            // No expanded.
            return false;
        }
        if (!(mBubbleData.getSelectedBubble() instanceof Bubble bubble) || !bubble.isApp()) {
            // Not app Bubble.
            return false;
        }
        final int expandedTaskId = bubble.getTaskId();
        for (int i = 0; i < info.getChanges().size(); i++) {
            final TransitionInfo.Change chg = info.getChanges().get(i);
            final ActivityManager.RunningTaskInfo taskInfo = chg.getTaskInfo();
            if (taskInfo == null || taskInfo.taskId != expandedTaskId) {
                continue;
            }
            // Check whether it is still an app bubble.
            return !shouldBeAppBubble(taskInfo);
        }
        return false;
    }

    /**
     * Plucks the task-surface out of an ancestor view while making the view invisible. This helper
     * attempts to do this seamlessly (ie. view becomes invisible in sync with task reparent).
+1 −2
Original line number Diff line number Diff line
@@ -863,8 +863,7 @@ public class DefaultMixedHandler implements MixedTransitionHandler,
        if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
            return null;
        }
        final TransitionInfo.Change change =
                DefaultMixedTransition.getChangeForBubblingTask(info, mBubbleTransitions);
        final TransitionInfo.Change change = mBubbleTransitions.getEnterBubbleTask(info);
        if (!com.android.wm.shell.Flags.fixTaskViewRotationAnimation()) {
            return change;
        }
+94 −47
Original line number Diff line number Diff line
@@ -16,11 +16,10 @@

package com.android.wm.shell.transition;

import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;

import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_REQUEST;
import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy;
import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit;
@@ -28,7 +27,6 @@ import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeygu

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
@@ -41,7 +39,6 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.phone.transition.PipTransitionUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.shared.pip.PipFlags;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -49,6 +46,7 @@ import com.android.wm.shell.splitscreen.StageCoordinator;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;

import java.util.List;
import java.util.function.Consumer;

class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
    private final UnfoldTransitionHandler mUnfoldHandler;
@@ -373,7 +371,7 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
                + "entering Bubbles while Split-Screen is foreground by %s", handler);

        TransitionInfo.Change bubblingTask = getChangeForBubblingTask(info, bubbleTransitions);
        final TransitionInfo.Change bubblingTask = bubbleTransitions.getEnterBubbleTask(info);
        // find previous split location for other task
        @SplitScreen.StageType int topSplitStageToKeep = SplitScreen.STAGE_TYPE_UNDEFINED;
        for (int i = info.getChanges().size() - 1; i >= 0; i--) {
@@ -414,6 +412,62 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
        return true;
    }

    /**
     * This is called when a task is being launched from a bubble, or when a task is launching to an
     * existing bubble. It may be one of the following cases, and each should be animated
     * differently:
     *  - Case 1: a Task was in an expanded Bubble, and a new Activity was launched on top of it
     *            from the task itself, or from a non-activity window, such as notification.
     *    - Pattern:
     *      - There is no open type Task in TransitionInfo (because it's an Activity transition).
     *      - A Task is Bubbled and expanded before and after the transition.
     *    - Expected Behavior:
     *      - Skip here.
     *      - Play Activity launch animation.
     *  - Case 2: a Task was in an expanded Bubble, and a new Activity was launched on top of it
     *            from a source activity of different windowing mode, such as Launcher.
     *    - Pattern:
     *      - There is a change type Task in TransitionInfo, which is no longer Bubbled.
     *    - Expected Behavior:
     *      - Skip here.
     *      - The Task should be dismissed from Bubble, and get opened in new windowing mode.
     *        Note: this shouldn't happen from normal user flow, and it now skipped here, but if it
     *              happens, there may not be a good animation.
     *  - Case 3: a Task was in an unfocused Bubble, a new Activity was launched to it from the
     *            focused expanded Bubble, or from a non-activity window, such as notification.
     *    - Pattern:
     *      - There is a move-to-front type Task in TransitionInfo, which is Bubbled.
     *      - That Task is Bubbled before and after the transition.
     *      - (Optional) There is a move-to-back type Task in TransitionInfo, which is Bubbled.
     *    - Expected Behavior:
     *      - Play expand Bubble animation.
     *      - (Optional) Hide the previous expanded Bubble.
     *  - Case 4: a Task was in an unfocused Bubble, a new Activity was launched to it from a source
     *            activity of different windowing mode, such as Launcher.
     *    - Pattern:
     *      - There is a move-to-front type Task in TransitionInfo, but is not Bubbled.
     *      - That Task was Bubbled before the transition.
     *    - Expected Behavior:
     *      - Skip here.
     *      - The Task should be dismissed from Bubble, and get opened in source's windowing mode.
     *  - Case 5: the source Task was in an expanded Bubble, it launched an Activity in new Task,
     *            and finished itself, such as Task trampoline.
     *    - Pattern:
     *      - There is an open type Task in TransitionInfo, which is Bubbled.
     *      - There is a close type Task in TransitionInfo, which is Bubbled.
     *    - Expected Behavior:
     *      - Jump cut, so the user should not see an extra animation for Task trampoline.
     *  - Case 6: the source Task was in an expanded Bubble, it launched an Activity in new Task,
     *            but didn't finish itself.
     *    - Pattern:
     *      - There is an opening Task in TransitionInfo, which is Bubbled.
     *      - (Optional) That Bubbled Task can be change/move-to-front type if it was in a different
     *        windowing mode before the transition.
     *      - A different Task was expanded Bubbled, but it may not be in TransitionInfo, as it may
     *        be closed later.
     *    - Expected Behavior:
     *      - Play Bubble switch animation.
     */
    static boolean animateEnterBubblesFromBubble(
            @NonNull IBinder transition,
            @NonNull TransitionInfo info,
@@ -422,53 +476,46 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition {
            @NonNull Transitions.TransitionFinishCallback finishCallback,
            @NonNull BubbleTransitions bubbleTransitions) {
        // Identify the task being launched into a bubble
        final TransitionInfo.Change change = getChangeForBubblingTask(info, bubbleTransitions);
        if (change == null) {
            // Fallback to remote transition scenarios, ex:
            // 1. Move bubble'd app to fullscreen for launcher icon clicked
            // 2. Launch activity in expanded and selected bubble for notification clicked
        final TransitionInfo.Change enterBubbleTask = bubbleTransitions.getEnterBubbleTask(info);
        if (enterBubbleTask == null) {
            // The trigger Task is no longer in Bubble (Case 1/2/4)
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " No bubbling task found");

            if (bubbleTransitions.containsNonBubbledExpandedTaskInStack(info)) {
                // The expanded Bubbled Task is no longer Bubbled (Case 2)
                ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                        " An activity launch converted the expanded Bubbled Task to non-Bubbled");
            }

            return false;
        }
        final TransitionInfo.Change closingBubble = bubbleTransitions.getClosingBubbleTask(info);

        // Task transition scenarios, ex:
        // 1. Start a new task from a bubbled task
        // 2. Expand the collapsed bubble for notification launch
        // 3. Switch the expanded bubble for notification launch
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
                + "entering bubble from another bubbled task or for an existing bubble");
        bubbleTransitions.startBubbleToBubbleLaunchOrExistingBubbleConvert(
                transition, change.getTaskInfo(), handler -> {
        final Consumer<Transitions.TransitionHandler> onInflatedCallback = handler -> {
            final Transitions.TransitionHandler h = bubbleTransitions
                    .getRunningEnterTransition(transition);
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation played by %s",
                    h);
            h.startAnimation(
                    transition, info, startTransaction, finishTransaction, finishCallback);
                });
        return true;
    }
        };

    static @Nullable TransitionInfo.Change getChangeForBubblingTask(
            @NonNull TransitionInfo info, BubbleTransitions bubbleTransitions) {
        for (int i = 0; i < info.getChanges().size(); i++) {
            final TransitionInfo.Change chg = info.getChanges().get(i);
            final ActivityManager.RunningTaskInfo taskInfo = chg.getTaskInfo();
            // Exclude activity transition scenarios.
            if (taskInfo == null || taskInfo.getActivityType() != ACTIVITY_TYPE_STANDARD) {
                continue;
            }
            // Only process opening or change transitions.
            if (!TransitionUtil.isOpeningMode(chg.getMode()) && chg.getMode() != TRANSIT_CHANGE) {
                continue;
            }
            // Skip non-app-bubble tasks (e.g., a reused task in a bubble-to-fullscreen scenario).
            if (!bubbleTransitions.shouldBeAppBubble(taskInfo)) {
                continue;
            }
            return chg;
        if (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);
        } else {
            // Opening a Bubble Task (Case 3/6)
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
                    + "entering bubble from another bubbled task or for an existing bubble");
            bubbleTransitions.startBubbleToBubbleLaunchOrExistingBubbleConvert(
                    transition, enterBubbleTask.getTaskInfo(), onInflatedCallback);
        }
        return null;

        return true;
    }

    private boolean animateUnfold(
+51 −1
Original line number Diff line number Diff line
@@ -16,7 +16,9 @@

package com.android.wm.shell.bubbles;

import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
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_FRONT;

@@ -172,6 +174,7 @@ public class BubbleTransitionsTest extends ShellTestCase {
        final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
        final WindowContainerToken token = new MockToken().token();
        taskInfo.token = token;
        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
        when(taskViewTaskController.getTaskInfo()).thenReturn(taskInfo);
        when(taskView.getController()).thenReturn(taskViewTaskController);
        when(mBubble.getTaskView()).thenReturn(taskView);
@@ -189,7 +192,10 @@ public class BubbleTransitionsTest extends ShellTestCase {
        when(mBubble.isApp()).thenReturn(true);
        when(mBubble.getIntent()).thenReturn(new Intent());
        when(mBubble.getUser()).thenReturn(new UserHandle(0));
        return setupBubble(taskView, taskViewTaskController);
        final ActivityManager.RunningTaskInfo taskInfo = setupBubble(
                taskView, taskViewTaskController);
        when(mBubbleController.shouldBeAppBubble(taskInfo)).thenReturn(true);
        return taskInfo;
    }

    private TransitionInfo setupFullscreenTaskTransition(ActivityManager.RunningTaskInfo taskInfo,
@@ -1073,4 +1079,48 @@ public class BubbleTransitionsTest extends ShellTestCase {
        // Now the queue should be empty
        assertThat(mTaskViewTransitions.hasPending()).isFalse();
    }

    @Test
    public void testGetEnterBubbleTask() {
        final SurfaceControl leash = new SurfaceControl.Builder().setName("testLeash").build();
        final ActivityManager.RunningTaskInfo taskInfo0 = setupAppBubble();
        final ActivityManager.RunningTaskInfo taskInfo1 = setupAppBubble();

        final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
        final TransitionInfo.Change openingBubble = new TransitionInfo.Change(
                taskInfo0.token, leash);
        openingBubble.setTaskInfo(taskInfo0);
        openingBubble.setMode(TRANSIT_OPEN);
        final TransitionInfo.Change closingBubble = new TransitionInfo.Change(
                taskInfo1.token, leash);
        closingBubble.setTaskInfo(taskInfo1);
        closingBubble.setMode(TRANSIT_CLOSE);
        info.addChange(closingBubble);
        info.addChange(openingBubble);
        info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0));

        assertThat(mBubbleTransitions.getEnterBubbleTask(info)).isEqualTo(openingBubble);
    }

    @Test
    public void testGetClosingBubbleTask() {
        final SurfaceControl leash = new SurfaceControl.Builder().setName("testLeash").build();
        final ActivityManager.RunningTaskInfo taskInfo0 = setupAppBubble();
        final ActivityManager.RunningTaskInfo taskInfo1 = setupAppBubble();

        final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
        final TransitionInfo.Change openingBubble = new TransitionInfo.Change(
                taskInfo0.token, leash);
        openingBubble.setTaskInfo(taskInfo0);
        openingBubble.setMode(TRANSIT_OPEN);
        final TransitionInfo.Change closingBubble = new TransitionInfo.Change(
                taskInfo1.token, leash);
        closingBubble.setTaskInfo(taskInfo1);
        closingBubble.setMode(TRANSIT_CLOSE);
        info.addChange(openingBubble);
        info.addChange(closingBubble);
        info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0));

        assertThat(mBubbleTransitions.getClosingBubbleTask(info)).isEqualTo(closingBubble);
    }
}
+22 −16
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.activityembedding.ActivityEmbeddingController
import com.android.wm.shell.bubbles.BubbleController
import com.android.wm.shell.bubbles.BubbleTransitions
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.keyguard.KeyguardTransitionHandler
@@ -50,6 +51,7 @@ import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify

@@ -70,7 +72,16 @@ class DefaultMixedHandlerTest : ShellTestCase() {
    private val desktopTasksController = mock<DesktopTasksController>()
    private val unfoldTransitionHandler = mock<UnfoldTransitionHandler>()
    private val activityEmbeddingController = mock<ActivityEmbeddingController>()
    private val bubbleTransitions = mock<BubbleTransitions>()
    private val bubbleController = mock<BubbleController>()
    private val bubbleTransitions = spy(BubbleTransitions(
        mContext,
        transitions,
        mock(),
        mock(),
        mock(),
        mock(),
        mock(),
    ))

    private val shellInit: ShellInit = ShellInit(TestShellExecutor())
    private val mixedHandler = DefaultMixedHandler(
@@ -89,6 +100,7 @@ class DefaultMixedHandlerTest : ShellTestCase() {
    @Before
    fun setUp() {
        shellInit.init()
        bubbleTransitions.setBubbleController(bubbleController)
    }

    @Test
@@ -120,7 +132,6 @@ class DefaultMixedHandlerTest : ShellTestCase() {

        bubbleTransitions.stub {
            on { hasPendingEnterTransition(request) } doReturn true
            on { isShowingAsBubbleBar } doReturn false
        }

        assertThat(mixedHandler.requestHasBubbleEnter(request)).isTrue()
@@ -134,7 +145,6 @@ class DefaultMixedHandlerTest : ShellTestCase() {

        bubbleTransitions.stub {
            on { hasPendingEnterTransition(request) } doReturn true
            on { isShowingAsBubbleBar } doReturn true
        }

        assertThat(mixedHandler.requestHasBubbleEnter(request)).isTrue()
@@ -149,8 +159,9 @@ class DefaultMixedHandlerTest : ShellTestCase() {

        bubbleTransitions.stub {
            on { hasPendingEnterTransition(request) } doReturn true
            on { isShowingAsBubbleBar } doReturn true
        }
        doReturn(mock<Transitions.TransitionHandler>()).`when`(bubbleTransitions)
            .storePendingEnterTransition(any(), any())

        mixedHandler.handleRequest(Binder(), request)
        verify(remoteTransition).onTransitionConsumed(any(), eq(false))
@@ -182,8 +193,7 @@ class DefaultMixedHandlerTest : ShellTestCase() {
        val runningTask = createRunningTask(100)
        val request = createTransitionRequestInfo(runningTask)

        bubbleTransitions.stub {
            on { isShowingAsBubbleBar } doReturn false
        bubbleController.stub {
            on { shouldBeAppBubble(runningTask) } doReturn true
        }

@@ -197,8 +207,7 @@ class DefaultMixedHandlerTest : ShellTestCase() {
        val runningTask = createRunningTask(100)
        val request = createTransitionRequestInfo(runningTask)

        bubbleTransitions.stub {
            on { isShowingAsBubbleBar } doReturn true
        bubbleController.stub {
            on { shouldBeAppBubble(runningTask) } doReturn true
        }

@@ -213,8 +222,7 @@ class DefaultMixedHandlerTest : ShellTestCase() {
        val remoteTransition = mock<IRemoteTransition>()
        val request = createTransitionRequestInfo(runningTask, RemoteTransition(remoteTransition))

        bubbleTransitions.stub {
            on { isShowingAsBubbleBar } doReturn true
        bubbleController.stub {
            on { shouldBeAppBubble(runningTask) } doReturn true
        }

@@ -228,8 +236,7 @@ class DefaultMixedHandlerTest : ShellTestCase() {
    fun test_startAnimation_NoBubbleEnterFromAppBubble() {
        val info = TransitionInfo(TRANSIT_OPEN, 0)

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

@@ -237,7 +244,7 @@ class DefaultMixedHandlerTest : ShellTestCase() {
            mock<SurfaceControl.Transaction>(), mock<Transitions.TransitionFinishCallback>())

        verify(bubbleTransitions, never()).startBubbleToBubbleLaunchOrExistingBubbleConvert(
            any(), any(), any());
            any(), any(), any())
    }

    @Test
@@ -249,8 +256,7 @@ class DefaultMixedHandlerTest : ShellTestCase() {
        val info = TransitionInfo(TRANSIT_OPEN, 0)
        info.addChange(change)

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

@@ -258,7 +264,7 @@ class DefaultMixedHandlerTest : ShellTestCase() {
            mock<SurfaceControl.Transaction>(), mock<Transitions.TransitionFinishCallback>())

        verify(bubbleTransitions).startBubbleToBubbleLaunchOrExistingBubbleConvert(
            any(), any(), any());
            any(), any(), any())
    }

    private fun createTransitionRequestInfo(