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

Commit dfe8620f authored by Ats Jenk's avatar Ats Jenk
Browse files

Add external gate transition to TaskViewTransitions

In some scenarios we want to block the TaskView transitions from running
until we complete some other work.
To accomplish this we were adding already running transitions as
external transitions to the TaskViewTransitions pending queue.

There was a scenario where these could not be cleared from the queue
anymore. If the queue already had something in there, the queued
external transition was not started immediately.
If we tried to clear this external transition from the queue, before it
was started, the removal failed. As the removal happens based on the
started transition identified. Which never got set.

The removal code was not aware of the failure to clear the external
transition. And did not retry.
Once the TaskViewTransitions queue reached the external transition, it
got started, but because the cleanup for it was already done, this
transition never got cleared from the pending queue.

This caused the queue to get stuck. Where it would not process any new
TaskView transitions.

To avoid this issue, we are adding a new way to enqueue external
transitions. If they do not need to be started, as in they are already
running, by the time we add them to the queue, add them as already
claimed.
They will still hold up the queue and require to be cleared, but this
ensures that they always can be cleared. And do not neccesarily need to
be started.

Bug: 433879169
Test: atest WMShellUnitTests:TaskViewTransitionsTest
Test: atest WMShellUnitTests:BubbleTransitionsTest
Flag: EXEMPT bugfix
Change-Id: Ia53b738755c29177ad1ebe3382d29e1ebc0a80bf
parent 2d27dcd6
Loading
Loading
Loading
Loading
+6 −4
Original line number Diff line number Diff line
@@ -217,8 +217,8 @@ public class BubbleTransitions {
            ProtoLog.d(
                    WM_SHELL_BUBBLES, "notifyUnfoldTransitionStarting transition=%s", transition);
            Bubble bubble = (Bubble) mBubbleData.getSelectedBubble();
            mTaskViewTransitions.enqueueExternal(
                    bubble.getTaskView().getController(), () -> transition);
            mTaskViewTransitions.enqueueRunningExternal(bubble.getTaskView().getController(),
                    transition);
        }
    }

@@ -539,7 +539,7 @@ public class BubbleTransitions {
            // 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);
            mTaskViewTransitions.enqueueRunningExternal(tv.getController(), mTransition);
        }

        @Override
@@ -826,6 +826,7 @@ public class BubbleTransitions {
                }
                opts.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
                opts.setLaunchBounds(launchBounds);
                // TODO(b/437451940): start the pending intent or shortcut via WCT
                if (mBubble.isShortcut()) {
                    final LauncherApps launcherApps = mContext.getSystemService(
                            LauncherApps.class);
@@ -1769,7 +1770,8 @@ public class BubbleTransitions {
            final TaskView tv = mBubble.getTaskView();
            mTransition = mTransitions.startTransition(TRANSIT_BUBBLE_CONVERT_FLOATING_TO_BAR,
                    mWct, this);
            mTaskViewTransitions.enqueueExternal(tv.getController(), () -> mTransition);
            mTaskViewTransitions.removePendingTransitions(tv.getController());
            mTaskViewTransitions.enqueueRunningExternal(tv.getController(), mTransition);
        }

        @Override
+1 −1
Original line number Diff line number Diff line
@@ -177,7 +177,7 @@ public class BubblesTransitionObserver implements Transitions.TransitionObserver
            // Notify the task removal, but block all TaskViewTransitions during removal so we can
            // clear them without triggering
            final IBinder gate = new Binder();
            mTaskViewTransitions.enqueueExternal(controller, () -> gate);
            mTaskViewTransitions.enqueueRunningExternal(controller, gate);

            taskOrganizer.applyTransaction(wct);
            controller.notifyTaskRemovalStarted(taskInfo);
+19 −0
Original line number Diff line number Diff line
@@ -217,6 +217,25 @@ public class TaskViewTransitions implements Transitions.TransitionHandler, TaskV
        startNextTransition();
    }

    /**
     * Add an already running external transition into the pending queue.
     * This transition has to be started externally. And it will block any new transitions from
     * starting in the pending queue.
     *
     * The external operation *must* call {@link #onExternalDone(IBinder)} once it has finished.
     */
    public void enqueueRunningExternal(@NonNull TaskViewTaskController taskView,
            IBinder transition) {
        ProtoLog.d(WM_SHELL_BUBBLES_NOISY,
                "Transitions.enqueueRunningExternal(): taskView=%d pending=%d",
                taskView.hashCode(), mPending.size());
        final PendingTransition pending = new PendingTransition(
                TRANSIT_NONE, null /* wct */, taskView, null /* cookie */);
        pending.mExternalTransition = () -> transition;
        pending.mClaimed = transition;
        mPending.add(pending);
    }

    /**
     * An external transition run in this "queue" is required to call this once it becomes ready.
     */
+79 −6
Original line number Diff line number Diff line
@@ -164,22 +164,32 @@ public class BubbleTransitionsTest extends ShellTestCase {
    }

    private ActivityManager.RunningTaskInfo setupBubble() {
        return setupBubble(mTaskView, mTaskViewTaskController);
    }

    private ActivityManager.RunningTaskInfo setupBubble(TaskView taskView,
            TaskViewTaskController taskViewTaskController) {
        final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
        final WindowContainerToken token = new MockToken().token();
        taskInfo.token = token;
        when(mTaskViewTaskController.getTaskInfo()).thenReturn(taskInfo);
        when(mTaskView.getController()).thenReturn(mTaskViewTaskController);
        when(mBubble.getTaskView()).thenReturn(mTaskView);
        when(mTaskView.getTaskInfo()).thenReturn(taskInfo);
        mRepository.add(mTaskViewTaskController);
        when(taskViewTaskController.getTaskInfo()).thenReturn(taskInfo);
        when(taskView.getController()).thenReturn(taskViewTaskController);
        when(mBubble.getTaskView()).thenReturn(taskView);
        when(taskView.getTaskInfo()).thenReturn(taskInfo);
        mRepository.add(taskViewTaskController);
        return taskInfo;
    }

    private ActivityManager.RunningTaskInfo setupAppBubble() {
        return setupAppBubble(mTaskView, mTaskViewTaskController);
    }

    private ActivityManager.RunningTaskInfo setupAppBubble(TaskView taskView,
            TaskViewTaskController taskViewTaskController) {
        when(mBubble.isApp()).thenReturn(true);
        when(mBubble.getIntent()).thenReturn(new Intent());
        when(mBubble.getUser()).thenReturn(new UserHandle(0));
        return setupBubble();
        return setupBubble(taskView, taskViewTaskController);
    }

    private TransitionInfo setupFullscreenTaskTransition(ActivityManager.RunningTaskInfo taskInfo,
@@ -966,4 +976,67 @@ public class BubbleTransitionsTest extends ShellTestCase {
        verify(mBubble).setPreparingTransition(null);
        assertThat(mTaskViewTransitions.hasPending()).isFalse();
    }

    /**
     * Test a scenario where the TaskViewTransitions queue has a pending TaskView transition. And
     * a new transition for launching a different bubble comes in during it. Once both transitions
     * are handled, the TaskViewTransitions pending queue should be empty.
     */
    @Test
    public void launchNewTaskBubbleForExistingTransition_withExistingTransitionInQueue() {
        // Set up a bubble and have it queue a transition in the queue that will remain pending
        TaskView existingTaskView = mock(TaskView.class);
        TaskViewTaskController existingTvc = mock(TaskViewTaskController.class);
        setupAppBubble(existingTaskView, existingTvc);
        final IBinder existingTransition = mock(IBinder.class);
        when(mTransitions.startTransition(anyInt(), any(), any())).thenReturn(existingTransition);
        mTaskViewTransitions.setTaskViewVisible(existingTvc, true);

        // Check that there is a pending transition before we create the new bubble
        assertThat(mTaskViewTransitions.hasPending()).isTrue();

        when(mLayerView.canExpandView(mBubble)).thenReturn(true);

        final ActivityManager.RunningTaskInfo bubbleTask = setupAppBubble();
        final IBinder transition = mock(IBinder.class);
        final BubbleTransitions.LaunchNewTaskBubbleForExistingTransition bt =
                (BubbleTransitions.LaunchNewTaskBubbleForExistingTransition) mBubbleTransitions
                        .startLaunchNewTaskBubbleForExistingTransition(
                                mBubble, mExpandedViewManager, mTaskViewFactory, mBubblePositioner,
                                mStackView, mLayerView, mIconFactory, false /* inflateSync */,
                                transition, transitionHandler -> {});

        verify(mBubble).setPreparingTransition(bt);

        // Prepare for startAnimation call
        final SurfaceControl taskLeash = new SurfaceControl.Builder().setName("taskLeash").build();
        final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
        final TransitionInfo.Change chg = new TransitionInfo.Change(bubbleTask.token, taskLeash);
        chg.setTaskInfo(bubbleTask);
        chg.setMode(TRANSIT_CHANGE);
        info.addChange(chg);
        info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0));

        final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
        final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
        final Transitions.TransitionFinishCallback finishCb = wct -> {};

        // Start playing the new bubble transition
        bt.startAnimation(transition, info, startT, finishT, finishCb);
        verify(mBubble, never()).setPreparingTransition(null);
        bt.onInflated(mBubble);
        bt.surfaceCreated();
        bt.continueExpand();

        // The pending queue should still have the transition from the existing bubble
        assertThat(mTaskViewTransitions.hasPending()).isTrue();

        // Now start the existing bubble transition
        mTaskViewTransitions.startAnimation(existingTransition,
                new TransitionInfo(TRANSIT_CHANGE, 0), startT, finishT, wct -> {
                });

        // Now the queue should be empty
        assertThat(mTaskViewTransitions.hasPending()).isFalse();
    }
}
+26 −0
Original line number Diff line number Diff line
@@ -446,6 +446,32 @@ public class TaskViewTransitionsTest extends ShellTestCase {
        assertThat(mTaskViewTransitions.hasPending()).isFalse();
    }

    @Test
    public void enqueueRunningExternal_clearedFromPendingWithoutStarting() {
        // Add a normal transition to the queue first.
        mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
        assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController,
                TRANSIT_TO_FRONT)).isNotNull();

        // Simulate an already running external transition by adding its binder ref.
        IBinder externalTransition = new Binder();
        mTaskViewTransitions.enqueueRunningExternal(mTaskViewTaskController, externalTransition);

        // Verify that both transitions are in the pending queue.
        assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController,
                TRANSIT_TO_FRONT)).isNotNull();
        assertThat(mTaskViewTransitions.findPending(externalTransition)).isNotNull();

        // Now, clear the external gate transition.
        mTaskViewTransitions.onExternalDone(externalTransition);

        // Verify that the external gate is removed, but the original transition is still pending.
        assertThat(mTaskViewTransitions.findPending(externalTransition)).isNull();
        assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController,
                TRANSIT_TO_FRONT)).isNotNull();
        assertThat(mTaskViewTransitions.hasPending()).isTrue();
    }

    private ActivityManager.RunningTaskInfo createMockTaskInfo(int taskId,
            WindowContainerToken token) {
        ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();