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

Commit 2be1c838 authored by Vinit Nayak's avatar Vinit Nayak Committed by Android (Google) Code Review
Browse files

Merge "Removed StageListenerImpl and moved data into StageTaskListener" into main

parents 343a4f16 8b4781e5
Loading
Loading
Loading
Loading
+63 −118
Original line number Diff line number Diff line
@@ -168,12 +168,10 @@ import java.util.concurrent.Executor;
 * - The {@link SplitLayout} divider is only visible if multiple {@link StageTaskListener}s are
 * visible
 * - Both stages are put under a single-top root task.
 * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
 * {@link #onStageHasChildrenChanged(StageListenerImpl).}
 */
public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler,
        ShellTaskOrganizer.TaskListener {
        ShellTaskOrganizer.TaskListener, StageTaskListener.StageListenerCallbacks {

    private static final String TAG = StageCoordinator.class.getSimpleName();

@@ -182,9 +180,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
    private static final int DISABLE_LAUNCH_ADJACENT_AFTER_ENTER_TIMEOUT_MS = 1000;

    private final StageTaskListener mMainStage;
    private final StageListenerImpl mMainStageListener = new StageListenerImpl();
    private final StageTaskListener mSideStage;
    private final StageListenerImpl mSideStageListener = new StageListenerImpl();
    @SplitPosition
    private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;

@@ -344,7 +340,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                mContext,
                mTaskOrganizer,
                mDisplayId,
                mMainStageListener,
                this /*stageListenerCallbacks*/,
                mSyncQueue,
                iconProvider,
                mWindowDecorViewModel);
@@ -352,7 +348,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                mContext,
                mTaskOrganizer,
                mDisplayId,
                mSideStageListener,
                this /*stageListenerCallbacks*/,
                mSyncQueue,
                iconProvider,
                mWindowDecorViewModel);
@@ -427,7 +423,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
    }

    public boolean isSplitScreenVisible() {
        return mSideStageListener.mVisible && mMainStageListener.mVisible;
        return mSideStage.mVisible && mMainStage.mVisible;
    }

    private void activateSplit(WindowContainerTransaction wct, boolean includingTopTask) {
@@ -1112,7 +1108,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        mSideStagePosition = sideStagePosition;
        sendOnStagePositionChanged();

        if (mSideStageListener.mVisible && updateBounds) {
        if (mSideStage.mVisible && updateBounds) {
            if (wct == null) {
                // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
                onLayoutSizeChanged(mSplitLayout);
@@ -1333,8 +1329,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,

    private void clearRequestIfPresented() {
        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearRequestIfPresented");
        if (mSideStageListener.mVisible && mSideStageListener.mHasChildren
                && mMainStageListener.mVisible && mSideStageListener.mHasChildren) {
        if (mSideStage.mVisible && mSideStage.mHasChildren
                && mMainStage.mVisible && mSideStage.mHasChildren) {
            mSplitRequest = null;
        }
    }
@@ -1587,11 +1583,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        }
    }

    private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
    @Override
    public void onChildTaskStatusChanged(StageTaskListener stageListener, int taskId,
            boolean present, boolean visible) {
        int stage;
        if (present) {
            stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
            stage = stageListener == mSideStage ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
        } else {
            // No longer on any stage
            stage = STAGE_TYPE_UNDEFINED;
@@ -1722,13 +1719,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,


    @VisibleForTesting
    void onRootTaskAppeared() {
    @Override
    public void onRootTaskAppeared() {
        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskAppeared: rootTask=%s mainRoot=%b sideRoot=%b",
                mRootTaskInfo, mMainStageListener.mHasRootTask, mSideStageListener.mHasRootTask);
                mRootTaskInfo, mMainStage.mHasRootTask, mSideStage.mHasRootTask);
        // Wait unit all root tasks appeared.
        if (mRootTaskInfo == null
                || !mMainStageListener.mHasRootTask
                || !mSideStageListener.mHasRootTask) {
                || !mMainStage.mHasRootTask
                || !mSideStage.mHasRootTask) {
            return;
        }

@@ -1751,11 +1749,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
    /** Callback when split roots have child task appeared under it, this is a little different from
     * #onStageHasChildrenChanged because this would be called every time child task appeared.
     * NOTICE: This only be called on legacy transition. */
    private void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) {
    @Override
    public void onChildTaskAppeared(StageTaskListener stageListener, int taskId) {
        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onChildTaskAppeared: isMainStage=%b task=%d",
                stageListener == mMainStageListener, taskId);
                stageListener == mMainStage, taskId);
        // Handle entering split screen while there is a split pair running in the background.
        if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
        if (stageListener == mSideStage && !isSplitScreenVisible() && isSplitActive()
                && mSplitRequest == null) {
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            prepareEnterSplitScreen(wct);
@@ -1776,7 +1775,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        }
    }

    private void onRootTaskVanished() {
    @Override
    public void onRootTaskVanished() {
        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onRootTaskVanished");
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        mLaunchAdjacentController.clearLaunchAdjacentRoot();
@@ -1793,15 +1793,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,

    /** Callback when split roots visiblility changed.
     * NOTICE: This only be called on legacy transition. */
    private void onStageVisibilityChanged(StageListenerImpl stageListener) {
    @Override
    public void onStageVisibilityChanged(StageTaskListener stageListener) {
        // If split didn't active, just ignore this callback because we should already did these
        // on #applyExitSplitScreen.
        if (!isSplitActive()) {
            return;
        }

        final boolean sideStageVisible = mSideStageListener.mVisible;
        final boolean mainStageVisible = mMainStageListener.mVisible;
        final boolean sideStageVisible = mSideStage.mVisible;
        final boolean mainStageVisible = mMainStage.mVisible;

        // Wait for both stages having the same visibility to prevent causing flicker.
        if (mainStageVisible != sideStageVisible) {
@@ -1938,18 +1939,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,

    /** Callback when split roots have child or haven't under it.
     * NOTICE: This only be called on legacy transition. */
    private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
    @Override
    public void onStageHasChildrenChanged(StageTaskListener stageListener) {
        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onStageHasChildrenChanged: isMainStage=%b",
                stageListener == mMainStageListener);
                stageListener == mMainStage);
        final boolean hasChildren = stageListener.mHasChildren;
        final boolean isSideStage = stageListener == mSideStageListener;
        final boolean isSideStage = stageListener == mSideStage;
        if (!hasChildren && !mIsExiting && isSplitActive()) {
            if (isSideStage && mMainStageListener.mVisible) {
            if (isSideStage && mMainStage.mVisible) {
                // Exit to main stage if side stage no longer has children.
                mSplitLayout.flingDividerToDismiss(
                        mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT,
                        EXIT_REASON_APP_FINISHED);
            } else if (!isSideStage && mSideStageListener.mVisible) {
            } else if (!isSideStage && mSideStage.mVisible) {
                // Exit to side stage if main stage no longer has children.
                mSplitLayout.flingDividerToDismiss(
                        mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
@@ -1974,7 +1976,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                }
            });
        }
        if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
        if (mMainStage.mHasChildren && mSideStage.mHasChildren) {
            mShouldUpdateRecents = true;
            clearRequestIfPresented();
            updateRecentTasksSplitPair();
@@ -1989,6 +1991,35 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        }
    }

    @Override
    public void onNoLongerSupportMultiWindow(StageTaskListener stageTaskListener,
            ActivityManager.RunningTaskInfo taskInfo) {
        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onNoLongerSupportMultiWindow: task=%s", taskInfo);
        if (isSplitActive()) {
            final boolean isMainStage = mMainStage == stageTaskListener;

            // If visible, we preserve the app and keep it running. If an app becomes
            // unsupported in the bg, break split without putting anything on top
            boolean splitScreenVisible = isSplitScreenVisible();
            int stageType = STAGE_TYPE_UNDEFINED;
            if (splitScreenVisible) {
                stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
            }
            final WindowContainerTransaction wct = new WindowContainerTransaction();
            prepareExitSplitScreen(stageType, wct);
            clearSplitPairedInRecents(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
            mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
                    EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
            Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
                    "app package " + taskInfo.baseIntent.getComponent()
                            + " does not support splitscreen, or is a controlled activity"
                            + " type"));
            if (splitScreenVisible) {
                handleUnsupportedSplitStart();
            }
        }
    }

    @Override
    public void onSnappedToDismiss(boolean bottomOrRight, @ExitReason int exitReason) {
        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onSnappedToDismiss: bottomOrRight=%b reason=%s",
@@ -3243,13 +3274,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        pw.println(childPrefix + "stagePosition=" + splitPositionToString(getMainStagePosition()));
        pw.println(childPrefix + "isActive=" + isSplitActive());
        mMainStage.dump(pw, childPrefix);
        pw.println(innerPrefix + "MainStageListener");
        mMainStageListener.dump(pw, childPrefix);
        pw.println(innerPrefix + "SideStage");
        pw.println(childPrefix + "stagePosition=" + splitPositionToString(getSideStagePosition()));
        mSideStage.dump(pw, childPrefix);
        pw.println(innerPrefix + "SideStageListener");
        mSideStageListener.dump(pw, childPrefix);
        if (mSplitLayout != null) {
            mSplitLayout.dump(pw, childPrefix);
        }
@@ -3265,8 +3292,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
     */
    private void setSplitsVisible(boolean visible) {
        ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setSplitsVisible: visible=%b", visible);
        mMainStageListener.mVisible = mSideStageListener.mVisible = visible;
        mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
        mMainStage.mVisible = mSideStage.mVisible = visible;
        mMainStage.mHasChildren = mSideStage.mHasChildren = visible;
    }

    /**
@@ -3316,86 +3343,4 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
                mSplitLayout.isLeftRightSplit());
    }

    class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
        boolean mHasRootTask = false;
        boolean mVisible = false;
        boolean mHasChildren = false;

        @Override
        public void onRootTaskAppeared() {
            mHasRootTask = true;
            StageCoordinator.this.onRootTaskAppeared();
        }

        @Override
        public void onChildTaskAppeared(int taskId) {
            StageCoordinator.this.onChildTaskAppeared(this, taskId);
        }

        @Override
        public void onStatusChanged(boolean visible, boolean hasChildren) {
            if (!mHasRootTask) return;

            if (mHasChildren != hasChildren) {
                mHasChildren = hasChildren;
                StageCoordinator.this.onStageHasChildrenChanged(this);
            }
            if (mVisible != visible) {
                mVisible = visible;
                StageCoordinator.this.onStageVisibilityChanged(this);
            }
        }

        @Override
        public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) {
            StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible);
        }

        @Override
        public void onRootTaskVanished() {
            reset();
            StageCoordinator.this.onRootTaskVanished();
        }

        @Override
        public void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo) {
            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onNoLongerSupportMultiWindow: task=%s", taskInfo);
            if (isSplitActive()) {
                final boolean isMainStage = mMainStageListener == this;

                // If visible, we preserve the app and keep it running. If an app becomes
                // unsupported in the bg, break split without putting anything on top
                boolean splitScreenVisible = isSplitScreenVisible();
                int stageType = STAGE_TYPE_UNDEFINED;
                if (splitScreenVisible) {
                    stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
                }
                final WindowContainerTransaction wct = new WindowContainerTransaction();
                prepareExitSplitScreen(stageType, wct);
                clearSplitPairedInRecents(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
                mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
                        EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
                Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
                        "app package " + taskInfo.baseIntent.getComponent()
                                + " does not support splitscreen, or is a controlled activity"
                                + " type"));
                if (splitScreenVisible) {
                    handleUnsupportedSplitStart();
                }
            }
        }

        private void reset() {
            mHasRootTask = false;
            mVisible = false;
            mHasChildren = false;
        }

        public void dump(@NonNull PrintWriter pw, String prefix) {
            pw.println(prefix + "mHasRootTask=" + mHasRootTask);
            pw.println(prefix + "mVisible=" + mVisible);
            pw.println(prefix + "mHasChildren=" + mHasChildren);
        }
    }
}
+39 −11
Original line number Diff line number Diff line
@@ -74,20 +74,22 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
    // No current way to enforce this but if enableFlexibleSplit() is enabled, then only 1 of the
    // stages should have this be set/being used
    private boolean mIsActive;

    /** Callback interface for listening to changes in a split-screen stage. */
    public interface StageListenerCallbacks {
        void onRootTaskAppeared();
        void onChildTaskAppeared(StageTaskListener stageTaskListener, int taskId);

        void onChildTaskAppeared(int taskId);
        void onStageHasChildrenChanged(StageTaskListener stageTaskListener);

        void onStatusChanged(boolean visible, boolean hasChildren);
        void onStageVisibilityChanged(StageTaskListener stageTaskListener);

        void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
        void onChildTaskStatusChanged(StageTaskListener stage, int taskId, boolean present,
                boolean visible);

        void onRootTaskVanished();

        void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo);
        void onNoLongerSupportMultiWindow(StageTaskListener stageTaskListener,
                ActivityManager.RunningTaskInfo taskInfo);
    }

    private final Context mContext;
@@ -96,6 +98,12 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
    private final IconProvider mIconProvider;
    private final Optional<WindowDecorViewModel> mWindowDecorViewModel;

    /** Whether or not the root task has been created. */
    boolean mHasRootTask = false;
    /** Whether or not the root task is visible. */
    boolean mVisible = false;
    /** Whether or not the root task has any children or not. */
    boolean mHasChildren = false;
    protected ActivityManager.RunningTaskInfo mRootTaskInfo;
    protected SurfaceControl mRootLeash;
    protected SurfaceControl mDimLayer;
@@ -201,6 +209,7 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
            mSplitDecorManager = new SplitDecorManager(
                    mRootTaskInfo.configuration,
                    mIconProvider);
            mHasRootTask = true;
            mCallbacks.onRootTaskAppeared();
            sendStatusChanged();
            mSyncQueue.runInSync(t -> mDimLayer =
@@ -209,14 +218,14 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
            final int taskId = taskInfo.taskId;
            mChildrenLeashes.put(taskId, leash);
            mChildrenTaskInfo.put(taskId, taskInfo);
            mCallbacks.onChildTaskStatusChanged(taskId, true /* present */,
            mCallbacks.onChildTaskStatusChanged(this, taskId, true /* present */,
                    taskInfo.isVisible && taskInfo.isVisibleRequested);
            if (ENABLE_SHELL_TRANSITIONS) {
                // Status is managed/synchronized by the transition lifecycle.
                return;
            }
            updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
            mCallbacks.onChildTaskAppeared(taskId);
            mCallbacks.onChildTaskAppeared(this, taskId);
            sendStatusChanged();
        } else {
            throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
@@ -250,11 +259,11 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
                        taskInfo.taskId);
                // Leave split screen if the task no longer supports multi window or have
                // uncontrolled task.
                mCallbacks.onNoLongerSupportMultiWindow(taskInfo);
                mCallbacks.onNoLongerSupportMultiWindow(this, taskInfo);
                return;
            }
            mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
            mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
            mCallbacks.onChildTaskStatusChanged(this, taskInfo.taskId, true /* present */,
                    taskInfo.isVisible && taskInfo.isVisibleRequested);
            if (!ENABLE_SHELL_TRANSITIONS) {
                updateChildTaskSurface(
@@ -278,6 +287,9 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
        final int taskId = taskInfo.taskId;
        mWindowDecorViewModel.ifPresent(vm -> vm.onTaskVanished(taskInfo));
        if (mRootTaskInfo.taskId == taskId) {
            mHasRootTask = false;
            mVisible = false;
            mHasChildren = false;
            mCallbacks.onRootTaskVanished();
            mRootTaskInfo = null;
            mRootLeash = null;
@@ -288,7 +300,8 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
        } else if (mChildrenTaskInfo.contains(taskId)) {
            mChildrenTaskInfo.remove(taskId);
            mChildrenLeashes.remove(taskId);
            mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
            mCallbacks.onChildTaskStatusChanged(this, taskId, false /* present */,
                    taskInfo.isVisible);
            if (ENABLE_SHELL_TRANSITIONS) {
                // Status is managed/synchronized by the transition lifecycle.
                return;
@@ -538,7 +551,19 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
    }

    private void sendStatusChanged() {
        mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0);
        boolean hasChildren = mChildrenTaskInfo.size() > 0;
        boolean visible = mRootTaskInfo.isVisible;
        if (!mHasRootTask) return;

        if (mHasChildren != hasChildren) {
            mHasChildren = hasChildren;
            mCallbacks.onStageHasChildrenChanged(this);
        }

        if (mVisible != visible) {
            mVisible = visible;
            mCallbacks.onStageVisibilityChanged(this);
        }
    }

    @Override
@@ -554,5 +579,8 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
                        + " baseActivity=" + taskInfo.baseActivity);
            }
        }
        pw.println(prefix + "mHasRootTask=" + mHasRootTask);
        pw.println(prefix + "mVisible=" + mVisible);
        pw.println(prefix + "mHasChildren=" + mHasChildren);
    }
}
+11 −30
Original line number Diff line number Diff line
@@ -23,10 +23,9 @@ import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.app.ActivityManager;
@@ -64,8 +63,6 @@ import java.util.Optional;
@SmallTest
@RunWith(AndroidJUnit4.class)
public final class StageTaskListenerTests extends ShellTestCase {
    private static final boolean ENABLE_SHELL_TRANSITIONS =
            SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);

    @Mock
    private ShellTaskOrganizer mTaskOrganizer;
@@ -117,20 +114,20 @@ public final class StageTaskListenerTests extends ShellTestCase {
    public void testRootTaskAppeared() {
        assertThat(mStageTaskListener.mRootTaskInfo.taskId).isEqualTo(mRootTask.taskId);
        verify(mCallbacks).onRootTaskAppeared();
        verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(false));
        verify(mCallbacks, never()).onStageHasChildrenChanged(mStageTaskListener);
        verify(mCallbacks, never()).onStageVisibilityChanged(mStageTaskListener);
    }

    @Test
    public void testChildTaskAppeared() {
        // With shell transitions, the transition manages status changes, so skip this test.
        assumeFalse(ENABLE_SHELL_TRANSITIONS);
        final ActivityManager.RunningTaskInfo childTask =
                new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
    public void testRootTaskVisible() {
        mStageTaskListener.onTaskVanished(mRootTask);
        mRootTask = new TestRunningTaskInfoBuilder().setVisible(true).build();
        mRootTask.parentTaskId = INVALID_TASK_ID;
        mSurfaceControl = new SurfaceControl.Builder().setName("test").build();
        mStageTaskListener.onTaskAppeared(mRootTask, mSurfaceControl);

        mStageTaskListener.onTaskAppeared(childTask, mSurfaceControl);
        verify(mCallbacks).onStageVisibilityChanged(mStageTaskListener);

        assertThat(mStageTaskListener.mChildrenTaskInfo.contains(childTask.taskId)).isTrue();
        verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true));
    }

    @Test(expected = IllegalArgumentException.class)
@@ -139,22 +136,6 @@ public final class StageTaskListenerTests extends ShellTestCase {
        mStageTaskListener.onTaskVanished(task);
    }

    @Test
    public void testTaskVanished() {
        // With shell transitions, the transition manages status changes, so skip this test.
        assumeFalse(ENABLE_SHELL_TRANSITIONS);
        final ActivityManager.RunningTaskInfo childTask =
                new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
        mStageTaskListener.mRootTaskInfo = mRootTask;
        mStageTaskListener.mChildrenTaskInfo.put(childTask.taskId, childTask);

        mStageTaskListener.onTaskVanished(childTask);
        verify(mCallbacks, times(2)).onStatusChanged(eq(mRootTask.isVisible), eq(false));

        mStageTaskListener.onTaskVanished(mRootTask);
        verify(mCallbacks).onRootTaskVanished();
    }

    @Test
    public void testTaskInfoChanged_notSupportsMultiWindow() {
        final ActivityManager.RunningTaskInfo childTask =
@@ -162,7 +143,7 @@ public final class StageTaskListenerTests extends ShellTestCase {
        childTask.supportsMultiWindow = false;

        mStageTaskListener.onTaskInfoChanged(childTask);
        verify(mCallbacks).onNoLongerSupportMultiWindow(childTask);
        verify(mCallbacks).onNoLongerSupportMultiWindow(mStageTaskListener, childTask);
    }

    @Test