diff options
9 files changed, 372 insertions, 98 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt index 1acde73e68dc..4723eb273988 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt @@ -119,7 +119,8 @@ class DesktopImmersiveController( ) } - fun moveTaskToNonImmersive(taskInfo: RunningTaskInfo) { + /** Starts a transition to move an immersive task out of immersive. */ + fun moveTaskToNonImmersive(taskInfo: RunningTaskInfo, reason: ExitReason) { if (inProgress) { logV( "Cannot start exit because transition(s) already in progress: %s", @@ -131,7 +132,7 @@ class DesktopImmersiveController( val wct = WindowContainerTransaction().apply { setBounds(taskInfo.token, getExitDestinationBounds(taskInfo)) } - logV("Moving task ${taskInfo.taskId} out of immersive mode") + logV("Moving task %d out of immersive mode, reason: %s", taskInfo.taskId, reason) val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this) state = TransitionState( transition = transition, @@ -151,10 +152,11 @@ class DesktopImmersiveController( fun exitImmersiveIfApplicable( transition: IBinder, wct: WindowContainerTransaction, - displayId: Int + displayId: Int, + reason: ExitReason, ) { if (!Flags.enableFullyImmersiveInDesktop()) return - val result = exitImmersiveIfApplicable(wct, displayId) + val result = exitImmersiveIfApplicable(wct, displayId, excludeTaskId = null, reason) result.asExit()?.runOnTransitionStart?.invoke(transition) } @@ -170,6 +172,7 @@ class DesktopImmersiveController( wct: WindowContainerTransaction, displayId: Int, excludeTaskId: Int? = null, + reason: ExitReason, ): ExitResult { if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit val immersiveTask = desktopRepository.getTaskInFullImmersiveState(displayId) @@ -179,7 +182,10 @@ class DesktopImmersiveController( } val taskInfo = shellTaskOrganizer.getRunningTaskInfo(immersiveTask) ?: return ExitResult.NoExit - logV("Appending immersive exit for task: $immersiveTask in display: $displayId") + logV( + "Appending immersive exit for task: %d in display: %d for reason: %s", + immersiveTask, displayId, reason + ) wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo)) return ExitResult.Exit( exitingTask = immersiveTask, @@ -198,14 +204,15 @@ class DesktopImmersiveController( */ fun exitImmersiveIfApplicable( wct: WindowContainerTransaction, - taskInfo: RunningTaskInfo + taskInfo: RunningTaskInfo, + reason: ExitReason, ): ExitResult { if (!Flags.enableFullyImmersiveInDesktop()) return ExitResult.NoExit if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) { // A full immersive task is being minimized, make sure the immersive state is broken // (i.e. resize back to max bounds). wct.setBounds(taskInfo.token, getExitDestinationBounds(taskInfo)) - logV("Appending immersive exit for task: ${taskInfo.taskId}") + logV("Appending immersive exit for task: %d for reason: %s", taskInfo.taskId, reason) return ExitResult.Exit( exitingTask = taskInfo.taskId, runOnTransitionStart = { transition -> @@ -550,6 +557,15 @@ class DesktopImmersiveController( ENTER, EXIT } + /** The reason for moving the task out of desktop immersive mode. */ + enum class ExitReason { + APP_NOT_IMMERSIVE, // The app stopped requesting immersive treatment. + USER_INTERACTION, // Explicit user intent request, e.g. a button click. + TASK_LAUNCH, // A task launched/moved on top of the immersive task. + MINIMIZED, // The immersive task was minimized. + CLOSED, // The immersive task was closed. + } + private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index a0bdd9fad510..eb930b0d9e00 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -70,7 +70,6 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.internal.policy.ScreenDecorationsUtils import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags -import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut import com.android.wm.shell.Flags.enableFlexibleSplit import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer @@ -133,6 +132,8 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeT import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.Companion.DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION +import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransitionState +import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING /** Handles moving tasks in and out of desktop */ class DesktopTasksController( @@ -209,7 +210,9 @@ class DesktopTasksController( val draggingTaskId get() = dragToDesktopTransitionHandler.draggingTaskId - private var recentsAnimationRunning = false + @RecentsTransitionState + private var recentsTransitionState = TRANSITION_STATE_NOT_RUNNING + private lateinit var splitScreenController: SplitScreenController lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter // Launch cookie used to identify a drag and drop transition to fullscreen after it has begun. @@ -238,10 +241,15 @@ class DesktopTasksController( dragToDesktopTransitionHandler.dragToDesktopStateListener = dragToDesktopStateListener recentsTransitionHandler.addTransitionStateListener( object : RecentsTransitionStateListener { - override fun onAnimationStateChanged(running: Boolean) { - logV("Recents animation state changed running=%b", running) - recentsAnimationRunning = running - desktopTilingDecorViewModel.onOverviewAnimationStateChange(running) + override fun onTransitionStateChanged(@RecentsTransitionState state: Int) { + logV( + "Recents transition state changed: %s", + RecentsTransitionStateListener.stateToString(state) + ) + recentsTransitionState = state + desktopTilingDecorViewModel.onOverviewAnimationStateChange( + RecentsTransitionStateListener.isAnimating(state) + ) } } ) @@ -381,6 +389,7 @@ class DesktopTasksController( wct = wct, displayId = DEFAULT_DISPLAY, excludeTaskId = taskId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, ) wct.startTask( taskId, @@ -413,6 +422,7 @@ class DesktopTasksController( wct = wct, displayId = task.displayId, excludeTaskId = task.taskId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, ) // Bring other apps to front first val taskIdToMinimize = @@ -460,7 +470,11 @@ class DesktopTasksController( bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId) addMoveToDesktopChanges(wct, taskInfo) val exitResult = desktopImmersiveController.exitImmersiveIfApplicable( - wct, taskInfo.displayId) + wct = wct, + displayId = taskInfo.displayId, + excludeTaskId = null, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH + ) val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct) desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted( DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS.toInt() @@ -508,8 +522,11 @@ class DesktopTasksController( taskId ) ) - return desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo).asExit() - ?.runOnTransitionStart + return desktopImmersiveController.exitImmersiveIfApplicable( + wct = wct, + taskInfo = taskInfo, + reason = DesktopImmersiveController.ExitReason.CLOSED + ).asExit()?.runOnTransitionStart } fun minimizeTask(taskInfo: RunningTaskInfo) { @@ -518,7 +535,11 @@ class DesktopTasksController( val wct = WindowContainerTransaction() performDesktopExitCleanupIfNeeded(taskId, wct) // Notify immersive handler as it might need to exit immersive state. - val exitResult = desktopImmersiveController.exitImmersiveIfApplicable(wct, taskInfo) + val exitResult = desktopImmersiveController.exitImmersiveIfApplicable( + wct = wct, + taskInfo = taskInfo, + reason = DesktopImmersiveController.ExitReason.MINIMIZED + ) wct.reorder(taskInfo.token, false) val transition = freeformTaskTransitionStarter.startMinimizedModeTransition(wct) @@ -675,6 +696,7 @@ class DesktopTasksController( wct = wct, displayId = displayId, excludeTaskId = launchingTaskId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, ) if (remoteTransition == null) { val t = desktopMixedTransitionHandler.startLaunchTransition( @@ -766,7 +788,10 @@ class DesktopTasksController( /** Moves a task in/out of full immersive state within the desktop. */ fun toggleDesktopTaskFullImmersiveState(taskInfo: RunningTaskInfo) { if (taskRepository.isTaskInFullImmersiveState(taskInfo.taskId)) { - exitDesktopTaskFromFullImmersive(taskInfo) + exitDesktopTaskFromFullImmersive( + taskInfo, + DesktopImmersiveController.ExitReason.USER_INTERACTION, + ) } else { moveDesktopTaskToFullImmersive(taskInfo) } @@ -777,9 +802,12 @@ class DesktopTasksController( desktopImmersiveController.moveTaskToImmersive(taskInfo) } - private fun exitDesktopTaskFromFullImmersive(taskInfo: RunningTaskInfo) { + private fun exitDesktopTaskFromFullImmersive( + taskInfo: RunningTaskInfo, + reason: DesktopImmersiveController.ExitReason, + ) { check(taskInfo.isFreeform) { "Task must already be in freeform" } - desktopImmersiveController.moveTaskToNonImmersive(taskInfo) + desktopImmersiveController.moveTaskToNonImmersive(taskInfo, reason) } /** @@ -1292,6 +1320,8 @@ class DesktopTasksController( // Check if we should skip handling this transition var reason = "" val triggerTask = request.triggerTask + val recentsAnimationRunning = + RecentsTransitionStateListener.isAnimating(recentsTransitionState) var shouldHandleMidRecentsFreeformLaunch = recentsAnimationRunning && isFreeformRelaunch(triggerTask, request) val isDragAndDropFullscreenTransition = taskContainsDragAndDropCookie(triggerTask) @@ -1479,6 +1509,7 @@ class DesktopTasksController( wct = wct, displayId = callingTask.displayId, excludeTaskId = requestedTaskId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, ) val transition = transitions.startTransition(TRANSIT_OPEN, wct, null) taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } @@ -1628,7 +1659,12 @@ class DesktopTasksController( } // Desktop Mode is showing and we're launching a new Task: // 1) Exit immersive if needed. - desktopImmersiveController.exitImmersiveIfApplicable(transition, wct, task.displayId) + desktopImmersiveController.exitImmersiveIfApplicable( + transition = transition, + wct = wct, + displayId = task.displayId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH, + ) // 2) minimize a Task if needed. val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId) addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize) @@ -1665,7 +1701,10 @@ class DesktopTasksController( taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize) desktopImmersiveController.exitImmersiveIfApplicable( - transition, wct, task.displayId + transition, + wct, + task.displayId, + reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH ) } } else if (taskRepository.isActiveTask(task.taskId)) { @@ -2282,9 +2321,13 @@ class DesktopTasksController( if (!Flags.enableFullyImmersiveInDesktop()) return val inImmersive = taskRepository.isTaskInFullImmersiveState(taskInfo.taskId) val requestingImmersive = taskInfo.requestingImmersive - if (inImmersive && !requestingImmersive) { + if (inImmersive && !requestingImmersive + && !RecentsTransitionStateListener.isRunning(recentsTransitionState)) { // Exit immersive if the app is no longer requesting it. - exitDesktopTaskFromFullImmersive(taskInfo) + exitDesktopTaskFromFullImmersive( + taskInfo, + DesktopImmersiveController.ExitReason.APP_NOT_IMMERSIVE + ) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 6da4f510ab77..d917f937b16c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -665,8 +665,10 @@ public class RecentTasksController implements TaskStackListenerCallback, } mTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() { @Override - public void onAnimationStateChanged(boolean running) { - executor.execute(() -> listener.accept(running)); + public void onTransitionStateChanged(@RecentsTransitionState int state) { + executor.execute(() -> { + listener.accept(RecentsTransitionStateListener.isAnimating(state)); + }); } }); }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 1c58dbbf71fd..032dac9ff3a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -32,6 +32,9 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; +import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING; +import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING; +import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED; import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION; import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS; import static com.android.wm.shell.transition.Transitions.TRANSIT_END_RECENTS_TRANSITION; @@ -166,13 +169,19 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, // only care about latest one. mAnimApp = appThread; + for (int i = 0; i < mStateListeners.size(); i++) { + mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_REQUESTED); + } // TODO(b/366021931): Formalize this later - final boolean isSyntheticRequest = options.containsKey("is_synthetic_recents_transition"); + final boolean isSyntheticRequest = options.getBoolean( + "is_synthetic_recents_transition", /* defaultValue= */ false); + final IBinder transition; if (isSyntheticRequest) { - return startSyntheticRecentsTransition(listener); + transition = startSyntheticRecentsTransition(listener); } else { - return startRealRecentsTransition(intent, fillIn, options, listener); + transition = startRealRecentsTransition(intent, fillIn, options, listener); } + return transition; } /** @@ -542,7 +551,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, mPendingFinishTransition = null; mControllers.remove(this); for (int i = 0; i < mStateListeners.size(); i++) { - mStateListeners.get(i).onAnimationStateChanged(false); + mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_NOT_RUNNING); } } @@ -578,7 +587,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, new RemoteAnimationTarget[0], new Rect(0, 0, 0, 0), new Rect(), new Bundle()); for (int i = 0; i < mStateListeners.size(); i++) { - mStateListeners.get(i).onAnimationStateChanged(true); + mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING); } } catch (RemoteException e) { Slog.e(TAG, "Error starting recents animation", e); @@ -809,7 +818,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]), new Rect(0, 0, 0, 0), new Rect(), b); for (int i = 0; i < mStateListeners.size(); i++) { - mStateListeners.get(i).onAnimationStateChanged(true); + mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING); } } catch (RemoteException e) { Slog.e(TAG, "Error starting recents animation", e); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java index 95874c8193c9..ea7cfd374f71 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java @@ -16,12 +16,47 @@ package com.android.wm.shell.recents; -import android.os.IBinder; +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** The listener for the events from {@link RecentsTransitionHandler}. */ public interface RecentsTransitionStateListener { - /** Notifies whether the recents animation is running. */ - default void onAnimationStateChanged(boolean running) { + @IntDef(prefix = { "TRANSITION_STATE_" }, value = { + TRANSITION_STATE_NOT_RUNNING, + TRANSITION_STATE_REQUESTED, + TRANSITION_STATE_ANIMATING, + }) + @Retention(RetentionPolicy.SOURCE) + @interface RecentsTransitionState {} + + int TRANSITION_STATE_NOT_RUNNING = 1; + int TRANSITION_STATE_REQUESTED = 2; + int TRANSITION_STATE_ANIMATING = 3; + + /** Notifies whether the recents transition state changes. */ + default void onTransitionStateChanged(@RecentsTransitionState int state) { + } + + /** Returns whether the recents transition is running. */ + static boolean isRunning(@RecentsTransitionState int state) { + return state >= TRANSITION_STATE_REQUESTED; + } + + /** Returns whether the recents transition is animating. */ + static boolean isAnimating(@RecentsTransitionState int state) { + return state >= TRANSITION_STATE_ANIMATING; + } + + /** Returns a string representation of the given state. */ + static String stateToString(@RecentsTransitionState int state) { + return switch (state) { + case TRANSITION_STATE_NOT_RUNNING -> "TRANSITION_STATE_NOT_RUNNING"; + case TRANSITION_STATE_REQUESTED -> "TRANSITION_STATE_REQUESTED"; + case TRANSITION_STATE_ANIMATING -> "TRANSITION_STATE_ANIMATING"; + default -> "UNKNOWN"; + }; } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java index f935ac76bbeb..9c31b46a80e5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java @@ -45,6 +45,7 @@ public final class TestRunningTaskInfoBuilder { private Intent mBaseIntent = new Intent(); private @WindowConfiguration.ActivityType int mActivityType = ACTIVITY_TYPE_STANDARD; private @WindowConfiguration.WindowingMode int mWindowingMode = WINDOWING_MODE_UNDEFINED; + private @WindowConfiguration.ActivityType int mTopActivityType = ACTIVITY_TYPE_STANDARD; private int mDisplayId = Display.DEFAULT_DISPLAY; private ActivityManager.TaskDescription.Builder mTaskDescriptionBuilder = null; private final Point mPositionInParent = new Point(); @@ -102,6 +103,12 @@ public final class TestRunningTaskInfoBuilder { return this; } + public TestRunningTaskInfoBuilder setTopActivityType( + @WindowConfiguration.ActivityType int activityType) { + mTopActivityType = activityType; + return this; + } + public TestRunningTaskInfoBuilder setWindowingMode( @WindowConfiguration.WindowingMode int windowingMode) { mWindowingMode = windowingMode; @@ -154,6 +161,7 @@ public final class TestRunningTaskInfoBuilder { info.configuration.windowConfiguration.setBounds(mBounds); info.configuration.windowConfiguration.setActivityType(mActivityType); info.configuration.windowConfiguration.setWindowingMode(mWindowingMode); + info.topActivityType = mTopActivityType; info.token = mToken; info.isResizeable = true; info.supportsMultiWindow = true; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt index a4f4d05d2079..4666276c2fae 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt @@ -42,6 +42,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitReason.USER_INTERACTION import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions @@ -168,7 +169,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.moveTaskToNonImmersive(task) + controller.moveTaskToNonImmersive(task, USER_INTERACTION) controller.onTransitionReady( transition = mockBinder, info = createTransitionInfo( @@ -195,7 +196,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { ) desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, Rect(100, 100, 600, 600)) - controller.moveTaskToNonImmersive(task) + controller.moveTaskToNonImmersive(task, USER_INTERACTION) controller.onTransitionReady( transition = mockBinder, info = createTransitionInfo( @@ -252,8 +253,8 @@ class DesktopImmersiveControllerTest : ShellTestCase() { whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))) .thenReturn(mockBinder) - controller.moveTaskToNonImmersive(task) - controller.moveTaskToNonImmersive(task) + controller.moveTaskToNonImmersive(task, USER_INTERACTION) + controller.moveTaskToNonImmersive(task, USER_INTERACTION) verify(mockTransitions, times(1)) .startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)) @@ -272,7 +273,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) assertThat(controller.pendingExternalExitTransitions.any { exit -> exit.transition == transition && exit.displayId == DEFAULT_DISPLAY @@ -293,7 +294,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = false ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) assertThat(controller.pendingExternalExitTransitions.any { exit -> exit.transition == transition && exit.displayId == DEFAULT_DISPLAY @@ -314,7 +315,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) assertThat(wct.hasBoundsChange(task.token)).isTrue() } @@ -332,7 +333,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = false ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) assertThat(wct.hasBoundsChange(task.token)).isFalse() } @@ -353,7 +354,8 @@ class DesktopImmersiveControllerTest : ShellTestCase() { controller.exitImmersiveIfApplicable( wct = wct, displayId = DEFAULT_DISPLAY, - excludeTaskId = task.taskId + excludeTaskId = task.taskId, + reason = USER_INTERACTION, ).asExit()?.runOnTransitionStart?.invoke(transition) assertThat(controller.pendingExternalExitTransitions.any { exit -> @@ -374,7 +376,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task) + controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION) assertThat(wct.hasBoundsChange(task.token)).isTrue() } @@ -391,7 +393,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = false ) - controller.exitImmersiveIfApplicable(wct, task) + controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION) assertThat(wct.hasBoundsChange(task.token)).isFalse() } @@ -409,7 +411,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.exitImmersiveIfApplicable(wct, task) + controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION) .asExit()?.runOnTransitionStart?.invoke(transition) assertThat(controller.pendingExternalExitTransitions.any { exit -> @@ -430,7 +432,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = false ) - val result = controller.exitImmersiveIfApplicable(wct, task) + val result = controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION) assertThat(result).isEqualTo(DesktopImmersiveController.ExitResult.NoExit) } @@ -447,7 +449,8 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = false ) - val result = controller.exitImmersiveIfApplicable(wct, task.displayId) + val result = controller.exitImmersiveIfApplicable( + wct, task.displayId, excludeTaskId = null, USER_INTERACTION) assertThat(result).isEqualTo(DesktopImmersiveController.ExitResult.NoExit) } @@ -464,7 +467,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { taskId = task.taskId, immersive = true ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) controller.onTransitionReady( transition = transition, @@ -495,7 +498,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { taskId = task.taskId, immersive = true ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) controller.onTransitionReady( transition = transition, @@ -530,7 +533,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { taskId = task.taskId, immersive = true ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) controller.onTransitionReady( transition = transition, @@ -560,7 +563,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, Rect(100, 100, 600, 600)) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) controller.onTransitionReady( transition = transition, @@ -587,7 +590,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task) + controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION) assertThat( wct.hasBoundsChange(task.token, calculateMaximizeBounds(mockDisplayLayout, task)) @@ -611,7 +614,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { val preImmersiveBounds = Rect(100, 100, 500, 500) desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, preImmersiveBounds) - controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task) + controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION) assertThat( wct.hasBoundsChange(task.token, preImmersiveBounds) @@ -634,7 +637,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task) + controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION) assertThat( wct.hasBoundsChange(task.token, calculateInitialBounds(mockDisplayLayout, task)) @@ -652,10 +655,10 @@ class DesktopImmersiveControllerTest : ShellTestCase() { taskId = task.taskId, immersive = true ) - controller.exitImmersiveIfApplicable(wct, task) + controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION) .asExit()?.runOnTransitionStart?.invoke(Binder()) - controller.moveTaskToNonImmersive(task) + controller.moveTaskToNonImmersive(task, USER_INTERACTION) verify(mockTransitions, never()).startTransition(any(), any(), any()) } @@ -674,7 +677,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY) + controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION) assertThat(controller.isImmersiveChange(transition, change)).isTrue() } @@ -692,7 +695,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() { immersive = true ) - controller.moveTaskToNonImmersive(task) + controller.moveTaskToNonImmersive(task, USER_INTERACTION) controller.animateResizeChange( change = TransitionInfo.Change(task.token, SurfaceControl()).apply { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 5df395754c7a..490e6b9a226b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -113,6 +113,8 @@ import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.recents.RecentTasksController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener +import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING +import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN import com.android.wm.shell.shared.split.SplitScreenConstants @@ -291,10 +293,10 @@ class DesktopTasksControllerTest : ShellTestCase() { tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda) whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), any<RunningTaskInfo>())) + .exitImmersiveIfApplicable(any(), any<RunningTaskInfo>(), any())) .thenReturn(ExitResult.NoExit) whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull())) + .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull(), any())) .thenReturn(ExitResult.NoExit) controller = createController() @@ -1793,7 +1795,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.minimizeTask(task) - verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task)) + verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task), any()) } @Test @@ -1803,7 +1805,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val runOnTransit = RunOnStartTransitionCallback() whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) .thenReturn(transition) - whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task))) + whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task), any())) .thenReturn( ExitResult.Exit( exitingTask = task.taskId, @@ -2192,7 +2194,7 @@ class DesktopTasksControllerTest : ShellTestCase() { markTaskVisible(freeformTask) // Mark recents animation running - recentsTransitionStateListener.onAnimationStateChanged(true) + recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING) // Open a fullscreen task, check that it does not result in a WCT with changes to it val fullscreenTask = createFullscreenTask() @@ -2206,7 +2208,7 @@ class DesktopTasksControllerTest : ShellTestCase() { markTaskVisible(freeformTask) // Mark recents animation running - recentsTransitionStateListener.onAnimationStateChanged(true) + recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING) // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA. val result = controller.handleRequest(Binder(), createTransition(freeformTask)) @@ -3189,7 +3191,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val wctCaptor = argumentCaptor<WindowContainerTransaction>() val transition = Binder() whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull())) + .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull(), any())) .thenReturn(ExitResult.NoExit) whenever(desktopMixedTransitionHandler .startLaunchTransition(anyInt(), any(), anyOrNull(), anyOrNull(), anyOrNull())) @@ -3212,7 +3214,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val runOnStart = RunOnStartTransitionCallback() val transition = Binder() whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull())) + .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull(), any())) .thenReturn(ExitResult.Exit(immersiveTask.taskId, runOnStart)) whenever(desktopMixedTransitionHandler .startLaunchTransition(anyInt(), any(), anyOrNull(), anyOrNull(), anyOrNull())) @@ -3315,7 +3317,8 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(transitions.startTransition(eq(TRANSIT_OPEN), any(), anyOrNull())) .thenReturn(transition) whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId), eq(freeformTask.taskId))) + .exitImmersiveIfApplicable( + any(), eq(immersiveTask.displayId), eq(freeformTask.taskId), any())) .thenReturn( ExitResult.Exit( exitingTask = immersiveTask.taskId, @@ -3325,7 +3328,7 @@ class DesktopTasksControllerTest : ShellTestCase() { runOpenInstance(immersiveTask, freeformTask.taskId) verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId), eq(freeformTask.taskId)) + .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId), eq(freeformTask.taskId), any()) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3740,7 +3743,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.toggleDesktopTaskFullImmersiveState(task) - verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(task) + verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(eq(task), any()) } @Test @@ -3752,7 +3755,7 @@ class DesktopTasksControllerTest : ShellTestCase() { task.requestedVisibleTypes = WindowInsets.Type.statusBars() controller.onTaskInfoChanged(task) - verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(task) + verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(eq(task), any()) } @Test @@ -3764,7 +3767,20 @@ class DesktopTasksControllerTest : ShellTestCase() { task.requestedVisibleTypes = WindowInsets.Type.statusBars() controller.onTaskInfoChanged(task) - verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(task) + verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(eq(task), any()) + } + + @Test + @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP) + fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_inRecentsTransition_noExit() { + val task = setUpFreeformTask(DEFAULT_DISPLAY) + taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true) + recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_REQUESTED) + + task.requestedVisibleTypes = WindowInsets.Type.statusBars() + controller.onTaskInfoChanged(task) + + verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(eq(task), any()) } @Test @@ -3774,7 +3790,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val runOnStartTransit = RunOnStartTransitionCallback() val transition = Binder() whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(wct, task.displayId, task.taskId)) + .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any())) .thenReturn( ExitResult.Exit( exitingTask = 5, @@ -3785,7 +3801,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN) verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(wct, task.displayId, task.taskId) + .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any()) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3796,7 +3812,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val runOnStartTransit = RunOnStartTransitionCallback() val transition = Binder() whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(wct, task.displayId, task.taskId)) + .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any())) .thenReturn( ExitResult.Exit( exitingTask = 5, @@ -3807,7 +3823,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN) verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(wct, task.displayId, task.taskId) + .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any()) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3817,7 +3833,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val runOnStartTransit = RunOnStartTransitionCallback() val transition = Binder() whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId))) + .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any())) .thenReturn( ExitResult.Exit( exitingTask = 5, @@ -3830,7 +3846,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveTaskToFront(task.taskId, remoteTransition = null) verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId)) + .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any()) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3840,7 +3856,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val runOnStartTransit = RunOnStartTransitionCallback() val transition = Binder() whenever(mMockDesktopImmersiveController - .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId))) + .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any())) .thenReturn( ExitResult.Exit( exitingTask = 5, @@ -3853,7 +3869,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveTaskToFront(task.taskId, remoteTransition = null) verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId)) + .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any()) runOnStartTransit.assertOnlyInvocation(transition) } @@ -3867,7 +3883,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.handleRequest(binder, createTransition(task)) verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId)) + .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId), any()) } @Test @@ -3879,7 +3895,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.handleRequest(binder, createTransition(task)) verify(mMockDesktopImmersiveController) - .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId)) + .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId), any()) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java index 6087763b4978..f0f5fe159069 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java @@ -16,7 +16,16 @@ package com.android.wm.shell.recents; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_TO_FRONT; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING; +import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING; +import static com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED; +import static com.android.wm.shell.transition.Transitions.TRANSIT_START_RECENTS_TRANSITION; + +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; @@ -27,6 +36,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.IApplicationThread; import android.app.KeyguardManager; @@ -38,7 +48,10 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.platform.test.flag.junit.SetFlagsRule; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -47,6 +60,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.internal.os.IResultReceiver; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; @@ -56,7 +70,9 @@ import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.HomeTransitionObserver; +import com.android.wm.shell.transition.TransitionInfoBuilder; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.StubTransaction; import org.junit.After; import org.junit.Before; @@ -93,6 +109,8 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { private IRecentTasksListener mRecentTasksListener; @Mock private TaskStackTransitionObserver mTaskStackTransitionObserver; + @Mock + private Transitions mTransitions; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -129,10 +147,9 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController), mMainExecutor); - final Transitions transitions = mock(Transitions.class); - doReturn(mMainExecutor).when(transitions).getMainExecutor(); + doReturn(mMainExecutor).when(mTransitions).getMainExecutor(); mRecentsTransitionHandler = new RecentsTransitionHandler(mShellInit, mShellTaskOrganizer, - transitions, mRecentTasksController, mock(HomeTransitionObserver.class)); + mTransitions, mRecentTasksController, mock(HomeTransitionObserver.class)); mShellInit.init(); } @@ -146,12 +163,8 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { public void testStartSyntheticRecentsTransition_callsOnAnimationStartAndFinishCallback() throws Exception { final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class); final IResultReceiver finishCallback = mock(IResultReceiver.class); - doReturn(new Binder()).when(runner).asBinder(); - Bundle options = new Bundle(); - options.putBoolean("is_synthetic_recents_transition", true); - IBinder transition = mRecentsTransitionHandler.startRecentsTransition( - mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class), - runner); + + final IBinder transition = startRecentsTransition(/* synthetic= */ true, runner); verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any()); // Finish and verify no transition remains and that the provided finish callback is called @@ -165,12 +178,8 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { @Test public void testStartSyntheticRecentsTransition_callsOnAnimationCancel() throws Exception { final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class); - doReturn(new Binder()).when(runner).asBinder(); - Bundle options = new Bundle(); - options.putBoolean("is_synthetic_recents_transition", true); - IBinder transition = mRecentsTransitionHandler.startRecentsTransition( - mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class), - runner); + + final IBinder transition = startRecentsTransition(/* synthetic= */ true, runner); verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any()); mRecentsTransitionHandler.findController(transition).cancel("test"); @@ -178,4 +187,137 @@ public class RecentsTransitionHandlerTest extends ShellTestCase { verify(runner).onAnimationCanceled(any(), any()); assertNull(mRecentsTransitionHandler.findController(transition)); } + + @Test + public void testStartTransition_updatesStateListeners() { + final TestTransitionStateListener listener = new TestTransitionStateListener(); + mRecentsTransitionHandler.addTransitionStateListener(listener); + + startRecentsTransition(/* synthetic= */ false); + mMainExecutor.flushAll(); + + assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_REQUESTED); + } + + @Test + public void testStartAnimation_updatesStateListeners() { + final TestTransitionStateListener listener = new TestTransitionStateListener(); + mRecentsTransitionHandler.addTransitionStateListener(listener); + + final IBinder transition = startRecentsTransition(/* synthetic= */ false); + mRecentsTransitionHandler.startAnimation( + transition, createTransitionInfo(), new StubTransaction(), new StubTransaction(), + mock(Transitions.TransitionFinishCallback.class)); + mMainExecutor.flushAll(); + + assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_ANIMATING); + } + + @Test + public void testFinishTransition_updatesStateListeners() { + final TestTransitionStateListener listener = new TestTransitionStateListener(); + mRecentsTransitionHandler.addTransitionStateListener(listener); + + final IBinder transition = startRecentsTransition(/* synthetic= */ false); + mRecentsTransitionHandler.startAnimation( + transition, createTransitionInfo(), new StubTransaction(), new StubTransaction(), + mock(Transitions.TransitionFinishCallback.class)); + mRecentsTransitionHandler.findController(transition).finish(true /* toHome */, + false /* sendUserLeaveHint */, mock(IResultReceiver.class)); + mMainExecutor.flushAll(); + + assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_NOT_RUNNING); + } + + @Test + public void testCancelTransition_updatesStateListeners() { + final TestTransitionStateListener listener = new TestTransitionStateListener(); + mRecentsTransitionHandler.addTransitionStateListener(listener); + + final IBinder transition = startRecentsTransition(/* synthetic= */ false); + mRecentsTransitionHandler.findController(transition).cancel("test"); + mMainExecutor.flushAll(); + + assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_NOT_RUNNING); + } + + @Test + public void testStartAnimation_synthetic_updatesStateListeners() { + final TestTransitionStateListener listener = new TestTransitionStateListener(); + mRecentsTransitionHandler.addTransitionStateListener(listener); + + startRecentsTransition(/* synthetic= */ true); + mMainExecutor.flushAll(); + + assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_ANIMATING); + } + + @Test + public void testFinishTransition_synthetic_updatesStateListeners() { + final TestTransitionStateListener listener = new TestTransitionStateListener(); + mRecentsTransitionHandler.addTransitionStateListener(listener); + + final IBinder transition = startRecentsTransition(/* synthetic= */ true); + mRecentsTransitionHandler.findController(transition).finish(true /* toHome */, + false /* sendUserLeaveHint */, mock(IResultReceiver.class)); + mMainExecutor.flushAll(); + + assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_NOT_RUNNING); + } + + @Test + public void testCancelTransition_synthetic_updatesStateListeners() { + final TestTransitionStateListener listener = new TestTransitionStateListener(); + mRecentsTransitionHandler.addTransitionStateListener(listener); + + final IBinder transition = startRecentsTransition(/* synthetic= */ true); + mRecentsTransitionHandler.findController(transition).cancel("test"); + mMainExecutor.flushAll(); + + assertThat(listener.getState()).isEqualTo(TRANSITION_STATE_NOT_RUNNING); + } + + private IBinder startRecentsTransition(boolean synthetic) { + return startRecentsTransition(synthetic, mock(IRecentsAnimationRunner.class)); + } + + private IBinder startRecentsTransition(boolean synthetic, + @NonNull IRecentsAnimationRunner runner) { + doReturn(new Binder()).when(runner).asBinder(); + final Bundle options = new Bundle(); + options.putBoolean("is_synthetic_recents_transition", synthetic); + final IBinder transition = new Binder(); + when(mTransitions.startTransition(anyInt(), any(), any())).thenReturn(transition); + return mRecentsTransitionHandler.startRecentsTransition( + mock(PendingIntent.class), new Intent(), options, mock(IApplicationThread.class), + runner); + } + + private TransitionInfo createTransitionInfo() { + final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder() + .setTopActivityType(ACTIVITY_TYPE_HOME) + .build(); + final TransitionInfo.Change homeChange = new TransitionInfo.Change( + task.token, new SurfaceControl()); + homeChange.setMode(TRANSIT_TO_FRONT); + homeChange.setTaskInfo(task); + return new TransitionInfoBuilder(TRANSIT_START_RECENTS_TRANSITION) + .addChange(homeChange) + .build(); + } + + private static class TestTransitionStateListener implements RecentsTransitionStateListener { + @RecentsTransitionState + private int mState = TRANSITION_STATE_NOT_RUNNING; + + @Override + public void onTransitionStateChanged(int state) { + mState = state; + } + + @RecentsTransitionState + int getState() { + return mState; + } + } } |