diff options
| author | 2024-12-16 16:34:32 -0800 | |
|---|---|---|
| committer | 2025-01-08 20:18:33 +0000 | |
| commit | f4ae6bf4e4f08d4387098536a2dbc8de6bb3412d (patch) | |
| tree | 1fe417e6f86b800d0298dd4e89a245bd5e9e3faa | |
| parent | 00b01090bca1e4c5fb433b034246124bfc4f0267 (diff) | |
[PiP2 on Desktop] Don't exit Desktop Mode when PiP is active.
Recall: http://recall/clips/99739260-beeb-4439-957a-79502a8eb8d1
With PiP on Desktop Mode, we should remain in Desktop Mode in the
following cases:
1) Minimizing the last open Desktop window into PiP
2) While PiP is running, another Desktop window that is the last
open Desktop window is minimized or closed
In both cases, closing the PiP window should then exit Desktop Mode.
If PiP is running and another Desktop window switches to fullscreen, we
will exit Desktop Mode but PiP will remain, and go to fullscreen when
expanded.
Test: atest DesktopTasksTransitionObserverTest DesktopRepositoryTest
DesktopTasksControllerTest PipSchedulerTest
Flag: com.android.window.flags.enable_desktop_windowing_pip
Bug: 385207728
Bug: 384445396
Change-Id: Ie44842dd2ece08638e4ca2e0f8916e76b8fc4dc6
13 files changed, 595 insertions, 56 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 2600bcc18f72..0495767f3ef1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -91,6 +91,7 @@ import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopUserRepositories; +import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.freeform.FreeformComponents; @@ -1033,6 +1034,24 @@ public abstract class WMShellBaseModule { }); } + @WMSingleton + @Provides + static DesktopWallpaperActivityTokenProvider provideDesktopWallpaperActivityTokenProvider() { + return new DesktopWallpaperActivityTokenProvider(); + } + + @WMSingleton + @Provides + static Optional<DesktopWallpaperActivityTokenProvider> + provideOptionalDesktopWallpaperActivityTokenProvider( + Context context, + DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider) { + if (DesktopModeStatus.canEnterDesktopMode(context)) { + return Optional.of(desktopWallpaperActivityTokenProvider); + } + return Optional.empty(); + } + // // Task Stack // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 1916215dea74..483c8008ba65 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -1312,12 +1312,6 @@ public abstract class WMShellModule { return new DesktopModeUiEventLogger(uiEventLogger, packageManager); } - @WMSingleton - @Provides - static DesktopWallpaperActivityTokenProvider provideDesktopWallpaperActivityTokenProvider() { - return new DesktopWallpaperActivityTokenProvider(); - } - // // Drag and drop // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 9e2b9b20be16..c8d0dab39837 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -41,6 +41,7 @@ import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.dagger.WMShellBaseModule; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.desktopmode.DesktopUserRepositories; +import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider; import com.android.wm.shell.pip2.phone.PhonePipMenuController; import com.android.wm.shell.pip2.phone.PipController; import com.android.wm.shell.pip2.phone.PipMotionHelper; @@ -82,11 +83,14 @@ public abstract class Pip2Module { @NonNull PipTransitionState pipStackListenerController, @NonNull PipDisplayLayoutState pipDisplayLayoutState, @NonNull PipUiStateChangeController pipUiStateChangeController, - Optional<DesktopUserRepositories> desktopUserRepositoriesOptional) { + Optional<DesktopUserRepositories> desktopUserRepositoriesOptional, + Optional<DesktopWallpaperActivityTokenProvider> + desktopWallpaperActivityTokenProviderOptional) { return new PipTransition(context, shellInit, shellTaskOrganizer, transitions, pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener, pipScheduler, pipStackListenerController, pipDisplayLayoutState, - pipUiStateChangeController, desktopUserRepositoriesOptional); + pipUiStateChangeController, desktopUserRepositoriesOptional, + desktopWallpaperActivityTokenProviderOptional); } @WMSingleton @@ -138,9 +142,12 @@ public abstract class Pip2Module { @ShellMainThread ShellExecutor mainExecutor, PipTransitionState pipTransitionState, Optional<DesktopUserRepositories> desktopUserRepositoriesOptional, + Optional<DesktopWallpaperActivityTokenProvider> + desktopWallpaperActivityTokenProviderOptional, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState, - desktopUserRepositoriesOptional, rootTaskDisplayAreaOrganizer); + desktopUserRepositoriesOptional, desktopWallpaperActivityTokenProviderOptional, + rootTaskDisplayAreaOrganizer); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index d3066645f32e..1a58363dab81 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -27,6 +27,7 @@ import androidx.core.util.forEach import androidx.core.util.keyIterator import androidx.core.util.valueIterator import com.android.internal.protolog.ProtoLog +import com.android.window.flags.Flags import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread @@ -56,6 +57,10 @@ class DesktopRepository( * @property topTransparentFullscreenTaskId the task id of any current top transparent * fullscreen task launched on top of Desktop Mode. Cleared when the transparent task is * closed or sent to back. (top is at index 0). + * @property pipTaskId the task id of PiP task entered while in Desktop Mode. + * @property pipShouldKeepDesktopActive whether an active PiP window should keep the Desktop + * Mode session active. Only false when we are explicitly exiting Desktop Mode (via user + * action) while there is an active PiP window. */ private data class DesktopTaskData( val activeTasks: ArraySet<Int> = ArraySet(), @@ -66,6 +71,8 @@ class DesktopRepository( val freeformTasksInZOrder: ArrayList<Int> = ArrayList(), var fullImmersiveTaskId: Int? = null, var topTransparentFullscreenTaskId: Int? = null, + var pipTaskId: Int? = null, + var pipShouldKeepDesktopActive: Boolean = true, ) { fun deepCopy(): DesktopTaskData = DesktopTaskData( @@ -76,6 +83,8 @@ class DesktopRepository( freeformTasksInZOrder = ArrayList(freeformTasksInZOrder), fullImmersiveTaskId = fullImmersiveTaskId, topTransparentFullscreenTaskId = topTransparentFullscreenTaskId, + pipTaskId = pipTaskId, + pipShouldKeepDesktopActive = pipShouldKeepDesktopActive, ) fun clear() { @@ -86,6 +95,8 @@ class DesktopRepository( freeformTasksInZOrder.clear() fullImmersiveTaskId = null topTransparentFullscreenTaskId = null + pipTaskId = null + pipShouldKeepDesktopActive = true } } @@ -104,6 +115,9 @@ class DesktopRepository( /* Tracks last bounds of task before toggled to immersive state. */ private val boundsBeforeFullImmersiveByTaskId = SparseArray<Rect>() + /* Callback for when a pending PiP transition has been aborted. */ + private var onPipAbortedCallback: ((Int, Int) -> Unit)? = null + private var desktopGestureExclusionListener: Consumer<Region>? = null private var desktopGestureExclusionExecutor: Executor? = null @@ -302,6 +316,54 @@ class DesktopRepository( } } + /** Set whether the given task is the Desktop-entered PiP task in this display. */ + fun setTaskInPip(displayId: Int, taskId: Int, enterPip: Boolean) { + val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId) + if (enterPip) { + desktopData.pipTaskId = taskId + desktopData.pipShouldKeepDesktopActive = true + } else { + desktopData.pipTaskId = + if (desktopData.pipTaskId == taskId) null + else { + logW( + "setTaskInPip: taskId=$taskId did not match saved taskId=${desktopData.pipTaskId}" + ) + desktopData.pipTaskId + } + } + notifyVisibleTaskListeners(displayId, getVisibleTaskCount(displayId)) + } + + /** Returns whether there is a PiP that was entered/minimized from Desktop in this display. */ + fun isMinimizedPipPresentInDisplay(displayId: Int): Boolean = + desktopTaskDataByDisplayId.getOrCreate(displayId).pipTaskId != null + + /** Returns whether the given task is the Desktop-entered PiP task in this display. */ + fun isTaskMinimizedPipInDisplay(displayId: Int, taskId: Int): Boolean = + desktopTaskDataByDisplayId.getOrCreate(displayId).pipTaskId == taskId + + /** Returns whether Desktop session should be active in this display due to active PiP. */ + fun shouldDesktopBeActiveForPip(displayId: Int): Boolean = + Flags.enableDesktopWindowingPip() && + isMinimizedPipPresentInDisplay(displayId) && + desktopTaskDataByDisplayId.getOrCreate(displayId).pipShouldKeepDesktopActive + + /** Saves whether a PiP window should keep Desktop session active in this display. */ + fun setPipShouldKeepDesktopActive(displayId: Int, keepActive: Boolean) { + desktopTaskDataByDisplayId.getOrCreate(displayId).pipShouldKeepDesktopActive = keepActive + } + + /** Saves callback to handle a pending PiP transition being aborted. */ + fun setOnPipAbortedCallback(callbackIfPipAborted: ((Int, Int) -> Unit)?) { + onPipAbortedCallback = callbackIfPipAborted + } + + /** Invokes callback to handle a pending PiP transition with the given task id being aborted. */ + fun onPipAborted(displayId: Int, pipTaskId: Int) { + onPipAbortedCallback?.invoke(displayId, pipTaskId) + } + /** Set whether the given task is the full-immersive task in this display. */ fun setTaskInFullImmersiveState(displayId: Int, taskId: Int, immersive: Boolean) { val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId) @@ -338,8 +400,12 @@ class DesktopRepository( } private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) { + val visibleAndPipTasksCount = + if (shouldDesktopBeActiveForPip(displayId)) visibleTasksCount + 1 else visibleTasksCount visibleTasksListeners.forEach { (listener, executor) -> - executor.execute { listener.onTasksVisibilityChanged(displayId, visibleTasksCount) } + executor.execute { + listener.onTasksVisibilityChanged(displayId, visibleAndPipTasksCount) + } } } 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 050dfb6f562c..6013648c9806 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 @@ -225,7 +225,6 @@ class DesktopTasksController( // Launch cookie used to identify a drag and drop transition to fullscreen after it has begun. // Used to prevent handleRequest from moving the new fullscreen task to freeform. private var dragAndDropFullscreenCookie: Binder? = null - private var pendingPipTransitionAndTask: Pair<IBinder, Int>? = null init { desktopMode = DesktopModeImpl() @@ -321,18 +320,29 @@ class DesktopTasksController( fun visibleTaskCount(displayId: Int): Int = taskRepository.getVisibleTaskCount(displayId) /** - * Returns true if any freeform tasks are visible or if a transparent fullscreen task exists on - * top in Desktop Mode. + * Returns true if any of the following is true: + * - Any freeform tasks are visible + * - A transparent fullscreen task exists on top in Desktop Mode + * - PiP on Desktop Windowing is enabled, there is an active PiP window and the desktop + * wallpaper is visible. */ fun isDesktopModeShowing(displayId: Int): Boolean { + val hasVisibleTasks = visibleTaskCount(displayId) > 0 + val hasTopTransparentFullscreenTask = + taskRepository.getTopTransparentFullscreenTaskId(displayId) != null + val hasMinimizedPip = + Flags.enableDesktopWindowingPip() && + taskRepository.isMinimizedPipPresentInDisplay(displayId) && + desktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(displayId) if ( DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC .isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() ) { - return visibleTaskCount(displayId) > 0 || - taskRepository.getTopTransparentFullscreenTaskId(displayId) != null + return hasVisibleTasks || hasTopTransparentFullscreenTask || hasMinimizedPip + } else if (Flags.enableDesktopWindowingPip()) { + return hasVisibleTasks || hasMinimizedPip } - return visibleTaskCount(displayId) > 0 + return hasVisibleTasks } /** Moves focused task to desktop mode for given [displayId]. */ @@ -592,7 +602,7 @@ class DesktopTasksController( ): ((IBinder) -> Unit)? { val taskId = taskInfo.taskId desktopTilingDecorViewModel.removeTaskIfTiled(displayId, taskId) - performDesktopExitCleanupIfNeeded(taskId, displayId, wct) + performDesktopExitCleanupIfNeeded(taskId, displayId, wct, forceToFullscreen = false) taskRepository.addClosingTask(displayId, taskId) taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate( doesAnyTaskRequireTaskbarRounding(displayId, taskId) @@ -624,8 +634,12 @@ class DesktopTasksController( ) val requestRes = transitions.dispatchRequest(Binder(), requestInfo, /* skip= */ null) wct.merge(requestRes.second, true) - pendingPipTransitionAndTask = - freeformTaskTransitionStarter.startPipTransition(wct) to taskInfo.taskId + freeformTaskTransitionStarter.startPipTransition(wct) + taskRepository.setTaskInPip(taskInfo.displayId, taskInfo.taskId, enterPip = true) + taskRepository.setOnPipAbortedCallback { displayId, taskId -> + minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!) + taskRepository.setTaskInPip(displayId, taskId, enterPip = false) + } return } @@ -636,7 +650,7 @@ class DesktopTasksController( val taskId = taskInfo.taskId val displayId = taskInfo.displayId val wct = WindowContainerTransaction() - performDesktopExitCleanupIfNeeded(taskId, displayId, wct) + performDesktopExitCleanupIfNeeded(taskId, displayId, wct, forceToFullscreen = false) // Notify immersive handler as it might need to exit immersive state. val exitResult = desktopImmersiveController.exitImmersiveIfApplicable( @@ -898,7 +912,12 @@ class DesktopTasksController( } if (Flags.enablePerDisplayDesktopWallpaperActivity()) { - performDesktopExitCleanupIfNeeded(task.taskId, task.displayId, wct) + performDesktopExitCleanupIfNeeded( + task.taskId, + task.displayId, + wct, + forceToFullscreen = false, + ) } transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) @@ -1414,7 +1433,9 @@ class DesktopTasksController( taskId: Int, displayId: Int, wct: WindowContainerTransaction, + forceToFullscreen: Boolean, ) { + taskRepository.setPipShouldKeepDesktopActive(displayId, !forceToFullscreen) if (Flags.enablePerDisplayDesktopWallpaperActivity()) { if (!taskRepository.isOnlyVisibleNonClosingTask(taskId, displayId)) { return @@ -1422,6 +1443,12 @@ class DesktopTasksController( if (displayId != DEFAULT_DISPLAY) { return } + } else if ( + Flags.enableDesktopWindowingPip() && + taskRepository.isMinimizedPipPresentInDisplay(displayId) && + !forceToFullscreen + ) { + return } else { if (!taskRepository.isOnlyVisibleNonClosingTask(taskId)) { return @@ -1462,21 +1489,6 @@ class DesktopTasksController( return false } - override fun onTransitionConsumed( - transition: IBinder, - aborted: Boolean, - finishT: Transaction?, - ) { - pendingPipTransitionAndTask?.let { (pipTransition, taskId) -> - if (transition == pipTransition) { - if (aborted) { - shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { minimizeTaskInner(it) } - } - pendingPipTransitionAndTask = null - } - } - } - override fun handleRequest( transition: IBinder, request: TransitionRequestInfo, @@ -1926,7 +1938,12 @@ class DesktopTasksController( if (!isDesktopModeShowing(task.displayId)) return null val wct = WindowContainerTransaction() - performDesktopExitCleanupIfNeeded(task.taskId, task.displayId, wct) + performDesktopExitCleanupIfNeeded( + task.taskId, + task.displayId, + wct, + forceToFullscreen = false, + ) if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) { taskRepository.addClosingTask(task.displayId, task.taskId) @@ -2053,7 +2070,12 @@ class DesktopTasksController( wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi()) } - performDesktopExitCleanupIfNeeded(taskInfo.taskId, taskInfo.displayId, wct) + performDesktopExitCleanupIfNeeded( + taskInfo.taskId, + taskInfo.displayId, + wct, + forceToFullscreen = true, + ) } private fun cascadeWindow(bounds: Rect, displayLayout: DisplayLayout, displayId: Int) { @@ -2087,7 +2109,12 @@ class DesktopTasksController( // want it overridden in multi-window. wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi()) - performDesktopExitCleanupIfNeeded(taskInfo.taskId, taskInfo.displayId, wct) + performDesktopExitCleanupIfNeeded( + taskInfo.taskId, + taskInfo.displayId, + wct, + forceToFullscreen = false, + ) } /** Returns the ID of the Task that will be minimized, or null if no task will be minimized. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index 9bf5555fc194..d61ffdaf5cf8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -21,9 +21,11 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.Context import android.os.IBinder import android.view.SurfaceControl -import android.view.WindowManager import android.view.WindowManager.TRANSIT_CLOSE +import android.view.WindowManager.TRANSIT_OPEN +import android.view.WindowManager.TRANSIT_PIP import android.view.WindowManager.TRANSIT_TO_BACK +import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.DesktopModeFlags import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY import android.window.TransitionInfo @@ -39,6 +41,8 @@ import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP +import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP /** * A [Transitions.TransitionObserver] that observes shell transitions and updates the @@ -57,6 +61,8 @@ class DesktopTasksTransitionObserver( ) : Transitions.TransitionObserver { private var transitionToCloseWallpaper: IBinder? = null + /* Pending PiP transition and its associated display id and task id. */ + private var pendingPipTransitionAndPipTask: Triple<IBinder, Int, Int>? = null private var currentProfileId: Int init { @@ -90,6 +96,33 @@ class DesktopTasksTransitionObserver( removeTaskIfNeeded(info) } removeWallpaperOnLastTaskClosingIfNeeded(transition, info) + + val desktopRepository = desktopUserRepositories.getProfile(currentProfileId) + info.changes.forEach { change -> + change.taskInfo?.let { taskInfo -> + if ( + Flags.enableDesktopWindowingPip() && + desktopRepository.isTaskMinimizedPipInDisplay( + taskInfo.displayId, + taskInfo.taskId, + ) + ) { + when (info.type) { + TRANSIT_PIP -> + pendingPipTransitionAndPipTask = + Triple(transition, taskInfo.displayId, taskInfo.taskId) + + TRANSIT_EXIT_PIP, + TRANSIT_REMOVE_PIP -> + desktopRepository.setTaskInPip( + taskInfo.displayId, + taskInfo.taskId, + enterPip = false, + ) + } + } + } + } } private fun removeTaskIfNeeded(info: TransitionInfo) { @@ -252,6 +285,18 @@ class DesktopTasksTransitionObserver( } } transitionToCloseWallpaper = null + } else if (pendingPipTransitionAndPipTask?.first == transition) { + val desktopRepository = desktopUserRepositories.getProfile(currentProfileId) + if (aborted) { + pendingPipTransitionAndPipTask?.let { + desktopRepository.onPipAborted( + /*displayId=*/ it.second, + /* taskId=*/ it.third, + ) + } + } + desktopRepository.setOnPipAbortedCallback(null) + pendingPipTransitionAndPipTask = null } } @@ -263,11 +308,15 @@ class DesktopTasksTransitionObserver( change.taskInfo?.let { taskInfo -> if (DesktopWallpaperActivity.isWallpaperTask(taskInfo)) { when (change.mode) { - WindowManager.TRANSIT_OPEN -> { + TRANSIT_OPEN -> { desktopWallpaperActivityTokenProvider.setToken( taskInfo.token, taskInfo.displayId, ) + desktopWallpaperActivityTokenProvider.setWallpaperActivityIsVisible( + isVisible = true, + taskInfo.displayId, + ) // After the task for the wallpaper is created, set it non-trimmable. // This is important to prevent recents from trimming and removing the // task. @@ -278,6 +327,16 @@ class DesktopTasksTransitionObserver( } TRANSIT_CLOSE -> desktopWallpaperActivityTokenProvider.removeToken(taskInfo.displayId) + TRANSIT_TO_FRONT -> + desktopWallpaperActivityTokenProvider.setWallpaperActivityIsVisible( + isVisible = true, + taskInfo.displayId, + ) + TRANSIT_TO_BACK -> + desktopWallpaperActivityTokenProvider.setWallpaperActivityIsVisible( + isVisible = false, + taskInfo.displayId, + ) else -> {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt index a87004c07d43..2bd7a9873a5e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.desktopmode.desktopwallpaperactivity import android.util.SparseArray +import android.util.SparseBooleanArray import android.view.Display.DEFAULT_DISPLAY import android.window.WindowContainerToken @@ -24,6 +25,7 @@ import android.window.WindowContainerToken class DesktopWallpaperActivityTokenProvider { private val wallpaperActivityTokenByDisplayId = SparseArray<WindowContainerToken>() + private val wallpaperActivityVisByDisplayId = SparseBooleanArray() fun setToken(token: WindowContainerToken, displayId: Int = DEFAULT_DISPLAY) { wallpaperActivityTokenByDisplayId[displayId] = token @@ -36,4 +38,16 @@ class DesktopWallpaperActivityTokenProvider { fun removeToken(displayId: Int = DEFAULT_DISPLAY) { wallpaperActivityTokenByDisplayId.delete(displayId) } + + fun setWallpaperActivityIsVisible( + isVisible: Boolean = false, + displayId: Int = DEFAULT_DISPLAY, + ) { + wallpaperActivityVisByDisplayId.put(displayId, isVisible) + } + + fun isWallpaperActivityVisible(displayId: Int = DEFAULT_DISPLAY): Boolean { + return wallpaperActivityTokenByDisplayId[displayId] != null && + wallpaperActivityVisByDisplayId.get(displayId, false) + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 7f673d2efc68..ea8dac982703 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -40,6 +40,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.desktopmode.DesktopUserRepositories; +import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; import com.android.wm.shell.pip2.animation.PipAlphaAnimator; @@ -59,6 +60,8 @@ public class PipScheduler { private final ShellExecutor mMainExecutor; private final PipTransitionState mPipTransitionState; private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional; + private final Optional<DesktopWallpaperActivityTokenProvider> + mDesktopWallpaperActivityTokenProviderOptional; private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private PipTransitionController mPipTransitionController; private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory @@ -73,12 +76,16 @@ public class PipScheduler { ShellExecutor mainExecutor, PipTransitionState pipTransitionState, Optional<DesktopUserRepositories> desktopUserRepositoriesOptional, + Optional<DesktopWallpaperActivityTokenProvider> + desktopWallpaperActivityTokenProviderOptional, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { mContext = context; mPipBoundsState = pipBoundsState; mMainExecutor = mainExecutor; mPipTransitionState = pipTransitionState; mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional; + mDesktopWallpaperActivityTokenProviderOptional = + desktopWallpaperActivityTokenProviderOptional; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mSurfaceControlTransactionFactory = @@ -260,10 +267,18 @@ public class PipScheduler { /** Returns whether PiP is exiting while we're in desktop mode. */ private boolean isPipExitingToDesktopMode() { - return Flags.enableDesktopWindowingPip() && mDesktopUserRepositoriesOptional.isPresent() - && (mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount( - Objects.requireNonNull(mPipTransitionState.getPipTaskInfo()).displayId) > 0 - || isDisplayInFreeform()); + // Early return if PiP in Desktop Windowing is not supported. + if (!Flags.enableDesktopWindowingPip() || mDesktopUserRepositoriesOptional.isEmpty() + || mDesktopWallpaperActivityTokenProviderOptional.isEmpty()) { + return false; + } + final int displayId = Objects.requireNonNull( + mPipTransitionState.getPipTaskInfo()).displayId; + return mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount(displayId) + > 0 + || mDesktopWallpaperActivityTokenProviderOptional.get().isWallpaperActivityVisible( + displayId) + || isDisplayInFreeform(); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 8061ee9090b6..38015ca6d45f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -63,7 +63,9 @@ import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipMenuController; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.split.SplitScreenUtils; +import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.DesktopUserRepositories; +import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip2.animation.PipAlphaAnimator; import com.android.wm.shell.pip2.animation.PipEnterAnimator; @@ -110,6 +112,8 @@ public class PipTransition extends PipTransitionController implements private final PipTransitionState mPipTransitionState; private final PipDisplayLayoutState mPipDisplayLayoutState; private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional; + private final Optional<DesktopWallpaperActivityTokenProvider> + mDesktopWallpaperActivityTokenProviderOptional; // // Transition caches @@ -145,7 +149,9 @@ public class PipTransition extends PipTransitionController implements PipTransitionState pipTransitionState, PipDisplayLayoutState pipDisplayLayoutState, PipUiStateChangeController pipUiStateChangeController, - Optional<DesktopUserRepositories> desktopUserRepositoriesOptional) { + Optional<DesktopUserRepositories> desktopUserRepositoriesOptional, + Optional<DesktopWallpaperActivityTokenProvider> + desktopWallpaperActivityTokenProviderOptional) { super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController, pipBoundsAlgorithm); @@ -157,6 +163,8 @@ public class PipTransition extends PipTransitionController implements mPipTransitionState.addPipTransitionStateChangedListener(this); mPipDisplayLayoutState = pipDisplayLayoutState; mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional; + mDesktopWallpaperActivityTokenProviderOptional = + desktopWallpaperActivityTokenProviderOptional; } @Override @@ -826,13 +834,14 @@ public class PipTransition extends PipTransitionController implements return false; } - // Since opening a new task while in Desktop Mode always first open in Fullscreen // until DesktopMode Shell code resolves it to Freeform, PipTransition will get a // possibility to handle it also. In this case return false to not have it enter PiP. final boolean isInDesktopSession = !mDesktopUserRepositoriesOptional.isEmpty() - && mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount( - pipTask.displayId) > 0; + && (mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount( + pipTask.displayId) > 0 + || mDesktopUserRepositoriesOptional.get().getCurrent() + .isMinimizedPipPresentInDisplay(pipTask.displayId)); if (isInDesktopSession) { return false; } @@ -968,6 +977,27 @@ public class PipTransition extends PipTransitionController implements "Unexpected bundle for " + mPipTransitionState); break; case PipTransitionState.EXITED_PIP: + final TaskInfo pipTask = mPipTransitionState.getPipTaskInfo(); + final boolean desktopPipEnabled = Flags.enableDesktopWindowingPip() + && mDesktopUserRepositoriesOptional.isPresent() + && mDesktopWallpaperActivityTokenProviderOptional.isPresent(); + if (desktopPipEnabled && pipTask != null) { + final DesktopRepository desktopRepository = + mDesktopUserRepositoriesOptional.get().getCurrent(); + final boolean wallpaperIsVisible = + mDesktopWallpaperActivityTokenProviderOptional.get() + .isWallpaperActivityVisible(pipTask.displayId); + if (desktopRepository.getVisibleTaskCount(pipTask.displayId) == 0 + && wallpaperIsVisible) { + mTransitions.startTransition( + TRANSIT_TO_BACK, + new WindowContainerTransaction().reorder( + mDesktopWallpaperActivityTokenProviderOptional.get() + .getToken(pipTask.displayId), /* onTop= */ false), + null + ); + } + } mPipTransitionState.setPinnedTaskLeash(null); mPipTransitionState.setPipTaskInfo(null); break; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt index 5629127b8c54..daecccef9344 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt @@ -25,6 +25,7 @@ import android.view.Display.DEFAULT_DISPLAY import android.view.Display.INVALID_DISPLAY import androidx.test.filters.SmallTest import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE +import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.ShellExecutor @@ -1067,6 +1068,67 @@ class DesktopRepositoryTest : ShellTestCase() { assertThat(repo.getTaskInFullImmersiveState(DEFAULT_DESKTOP_ID + 1)).isEqualTo(2) } + @Test + fun setTaskInPip_savedAsMinimizedPipInDisplay() { + assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isFalse() + + repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true) + + assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue() + } + + @Test + fun removeTaskInPip_removedAsMinimizedPipInDisplay() { + repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true) + assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue() + + repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = false) + + assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isFalse() + } + + @Test + fun setTaskInPip_multipleDisplays_bothAreInPip() { + repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true) + repo.setTaskInPip(DEFAULT_DESKTOP_ID + 1, taskId = 2, enterPip = true) + + assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue() + assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID + 1, taskId = 2)).isTrue() + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun setPipShouldKeepDesktopActive_shouldKeepDesktopActive() { + assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse() + + repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true) + repo.setPipShouldKeepDesktopActive(DEFAULT_DESKTOP_ID, keepActive = true) + + assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isTrue() + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun setPipShouldNotKeepDesktopActive_shouldNotKeepDesktopActive() { + repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true) + assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isTrue() + + repo.setPipShouldKeepDesktopActive(DEFAULT_DESKTOP_ID, keepActive = false) + + assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse() + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun removeTaskInPip_shouldNotKeepDesktopActive() { + repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true) + assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isTrue() + + repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = false) + + assertThat(repo.shouldDesktopBeActiveForPip(DEFAULT_DESKTOP_ID)).isFalse() + } + class TestListener : DesktopRepository.ActiveTasksListener { var activeChangesOnDefaultDisplay = 0 var activeChangesOnSecondaryDisplay = 0 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 692b50303038..4bb743079861 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 @@ -82,6 +82,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.internal.jank.InteractionJankMonitor import com.android.window.flags.Flags import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE +import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP import com.android.window.flags.Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS import com.android.window.flags.Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT @@ -569,6 +570,38 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun isDesktopModeShowing_minimizedPipTask_wallpaperVisible_returnsTrue() { + val pipTask = setUpPipTask(autoEnterEnabled = true) + whenever(desktopWallpaperActivityTokenProvider.isWallpaperActivityVisible()) + .thenReturn(true) + + taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = true) + + assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isTrue() + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun isDesktopModeShowing_minimizedPipTask_wallpaperNotVisible_returnsFalse() { + val pipTask = setUpPipTask(autoEnterEnabled = true) + whenever(desktopWallpaperActivityTokenProvider.isWallpaperActivityVisible()) + .thenReturn(false) + + taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = true) + + assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isFalse() + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun isDesktopModeShowing_pipTaskNotMinimizedNorVisible_returnsFalse() { + setUpPipTask(autoEnterEnabled = true) + + assertThat(controller.isDesktopModeShowing(displayId = DEFAULT_DISPLAY)).isFalse() + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() { val homeTask = setUpHomeTask(SECOND_DISPLAY) @@ -2039,6 +2072,41 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun onDesktopWindowClose_minimizedPipPresent_doesNotExitDesktop() { + val freeformTask = setUpFreeformTask().apply { isFocused = true } + val pipTask = setUpPipTask(autoEnterEnabled = true) + + taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = true) + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, freeformTask) + + verifyExitDesktopWCTNotExecuted() + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun onDesktopWindowClose_minimizedPipNotPresent_exitDesktop() { + val freeformTask = setUpFreeformTask() + val pipTask = setUpPipTask(autoEnterEnabled = true) + val handler = mock(TransitionHandler::class.java) + whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) + .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) + + controller.minimizeTask(pipTask) + verifyExitDesktopWCTNotExecuted() + + taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = false) + val wct = WindowContainerTransaction() + controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, freeformTask) + + // Remove wallpaper operation + wct.hierarchyOps.any { hop -> + hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() + } + } + + @Test fun onDesktopWindowMinimize_noActiveTask_doesntRemoveWallpaper() { val task = setUpFreeformTask(active = false) val transition = Binder() @@ -2055,10 +2123,9 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun onDesktopWindowMinimize_pipTask_autoEnterEnabled_startPipTransition() { + fun onPipTaskMinimize_autoEnterEnabled_startPipTransition() { val task = setUpPipTask(autoEnterEnabled = true) val handler = mock(TransitionHandler::class.java) - whenever(freeformTaskTransitionStarter.startPipTransition(any())).thenReturn(Binder()) whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) @@ -2069,7 +2136,7 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun onDesktopWindowMinimize_pipTask_autoEnterDisabled_startMinimizeTransition() { + fun onPipTaskMinimize_autoEnterDisabled_startMinimizeTransition() { val task = setUpPipTask(autoEnterEnabled = false) whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) .thenReturn(Binder()) @@ -2081,6 +2148,22 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun onPipTaskMinimize_doesntRemoveWallpaper() { + val task = setUpPipTask(autoEnterEnabled = true) + val handler = mock(TransitionHandler::class.java) + whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) + .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) + + controller.minimizeTask(task) + + val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + verify(freeformTaskTransitionStarter).startPipTransition(captor.capture()) + captor.value.hierarchyOps.none { hop -> + hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() + } + } + + @Test fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() { val task = setUpFreeformTask(active = true) val transition = Binder() @@ -3125,6 +3208,31 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun moveFocusedTaskToFullscreen_minimizedPipPresent_removeWallpaperActivity() { + val freeformTask = setUpFreeformTask() + val pipTask = setUpPipTask(autoEnterEnabled = true) + val handler = mock(TransitionHandler::class.java) + whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) + .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) + + controller.minimizeTask(pipTask) + verifyExitDesktopWCTNotExecuted() + + freeformTask.isFocused = true + controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN) + + val wct = getLatestExitDesktopWct() + val taskChange = assertNotNull(wct.changes[freeformTask.token.asBinder()]) + assertThat(taskChange.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN + // Remove wallpaper operation + wct.hierarchyOps.any { hop -> + hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder() + } + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION) fun removeDesktop_multipleTasks_removesAll() { val task1 = setUpFreeformTask() @@ -4851,7 +4959,8 @@ class DesktopTasksControllerTest : ShellTestCase() { } private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo { - return setUpFreeformTask().apply { + // active = false marks the task as non-visible; PiP window doesn't count as visible tasks + return setUpFreeformTask(active = false).apply { pictureInPictureParams = PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build() } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt index 89ab65a42bbf..96ed214e7f88 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt @@ -22,6 +22,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.content.ComponentName import android.content.Context import android.content.Intent +import android.os.Binder import android.os.IBinder import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule @@ -29,7 +30,9 @@ import android.view.Display.DEFAULT_DISPLAY import android.view.WindowManager import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_OPEN +import android.view.WindowManager.TRANSIT_PIP import android.view.WindowManager.TRANSIT_TO_BACK +import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.IWindowContainerToken import android.window.TransitionInfo import android.window.TransitionInfo.Change @@ -38,6 +41,7 @@ import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.window.flags.Flags +import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP import com.android.wm.shell.MockToken import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.back.BackAnimationController @@ -47,6 +51,8 @@ import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpape import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP +import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import org.junit.Before @@ -300,6 +306,115 @@ class DesktopTasksTransitionObserverTest { verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId) } + @Test + fun transitOpenWallpaper_wallpaperActivityVisibilitySaved() { + val wallpaperTask = createWallpaperTaskInfo() + + transitionObserver.onTransitionReady( + transition = mock(), + info = createOpenChangeTransition(wallpaperTask), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(desktopWallpaperActivityTokenProvider) + .setWallpaperActivityIsVisible(isVisible = true, wallpaperTask.displayId) + } + + @Test + fun transitToFrontWallpaper_wallpaperActivityVisibilitySaved() { + val wallpaperTask = createWallpaperTaskInfo() + + transitionObserver.onTransitionReady( + transition = mock(), + info = createToFrontTransition(wallpaperTask), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(desktopWallpaperActivityTokenProvider) + .setWallpaperActivityIsVisible(isVisible = true, wallpaperTask.displayId) + } + + @Test + fun transitToBackWallpaper_wallpaperActivityVisibilitySaved() { + val wallpaperTask = createWallpaperTaskInfo() + + transitionObserver.onTransitionReady( + transition = mock(), + info = createToBackTransition(wallpaperTask), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(desktopWallpaperActivityTokenProvider) + .setWallpaperActivityIsVisible(isVisible = false, wallpaperTask.displayId) + } + + @Test + fun transitCloseWallpaper_wallpaperActivityVisibilitySaved() { + val wallpaperTask = createWallpaperTaskInfo() + + transitionObserver.onTransitionReady( + transition = mock(), + info = createCloseTransition(wallpaperTask), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(desktopWallpaperActivityTokenProvider).removeToken(wallpaperTask.displayId) + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun pendingPipTransitionAborted_taskRepositoryOnPipAbortedInvoked() { + val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM) + val pipTransition = Binder() + whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true) + + transitionObserver.onTransitionReady( + transition = pipTransition, + info = createOpenChangeTransition(task, TRANSIT_PIP), + startTransaction = mock(), + finishTransaction = mock(), + ) + transitionObserver.onTransitionFinished(transition = pipTransition, aborted = true) + + verify(taskRepository).onPipAborted(task.displayId, task.taskId) + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun exitPipTransition_taskRepositoryClearTaskInPip() { + val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM) + whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true) + + transitionObserver.onTransitionReady( + transition = mock(), + info = createOpenChangeTransition(task, type = TRANSIT_EXIT_PIP), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false) + } + + @Test + @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP) + fun removePipTransition_taskRepositoryClearTaskInPip() { + val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM) + whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true) + + transitionObserver.onTransitionReady( + transition = mock(), + info = createOpenChangeTransition(task, type = TRANSIT_REMOVE_PIP), + startTransaction = mock(), + finishTransaction = mock(), + ) + + verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false) + } + private fun createBackNavigationTransition( task: RunningTaskInfo?, type: Int = TRANSIT_TO_BACK, @@ -331,7 +446,7 @@ class DesktopTasksTransitionObserverTest { task: RunningTaskInfo?, type: Int = TRANSIT_OPEN, ): TransitionInfo { - return TransitionInfo(TRANSIT_OPEN, /* flags= */ 0).apply { + return TransitionInfo(type, /* flags= */ 0).apply { addChange( Change(mock(), mock()).apply { mode = TRANSIT_OPEN @@ -369,6 +484,19 @@ class DesktopTasksTransitionObserverTest { } } + private fun createToFrontTransition(task: RunningTaskInfo?): TransitionInfo { + return TransitionInfo(TRANSIT_TO_FRONT, 0 /* flags */).apply { + addChange( + Change(mock(), mock()).apply { + mode = TRANSIT_TO_FRONT + parent = null + taskInfo = task + flags = flags + } + ) + } + } + private fun getLatestWct( @WindowManager.TransitionType type: Int = TRANSIT_OPEN, handlerClass: Class<out Transitions.TransitionHandler>? = null, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java index a8aa25700c7e..c42f6c35bcb0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java @@ -30,6 +30,7 @@ import static org.mockito.kotlin.MatchersKt.eq; import static org.mockito.kotlin.VerificationKt.times; import static org.mockito.kotlin.VerificationKt.verify; +import android.app.TaskInfo; import android.content.Context; import android.content.res.Resources; import android.graphics.Matrix; @@ -45,7 +46,9 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.desktopmode.DesktopUserRepositories; +import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; import com.android.wm.shell.pip2.animation.PipAlphaAnimator; @@ -56,6 +59,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.Optional; @@ -83,7 +87,8 @@ public class PipSchedulerTest { @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory; @Mock private SurfaceControl.Transaction mMockTransaction; @Mock private PipAlphaAnimator mMockAlphaAnimator; - @Mock private Optional<DesktopUserRepositories> mMockOptionalDesktopUserRepositories; + @Mock private DesktopUserRepositories mMockDesktopUserRepositories; + @Mock private DesktopWallpaperActivityTokenProvider mMockDesktopWallpaperActivityTokenProvider; @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; @Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor; @@ -100,9 +105,13 @@ public class PipSchedulerTest { when(mMockFactory.getTransaction()).thenReturn(mMockTransaction); when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) .thenReturn(mMockTransaction); + when(mMockDesktopUserRepositories.getCurrent()) + .thenReturn(Mockito.mock(DesktopRepository.class)); + when(mMockPipTransitionState.getPipTaskInfo()).thenReturn(Mockito.mock(TaskInfo.class)); mPipScheduler = new PipScheduler(mMockContext, mMockPipBoundsState, mMockMainExecutor, - mMockPipTransitionState, mMockOptionalDesktopUserRepositories, + mMockPipTransitionState, Optional.of(mMockDesktopUserRepositories), + Optional.of(mMockDesktopWallpaperActivityTokenProvider), mRootTaskDisplayAreaOrganizer); mPipScheduler.setPipTransitionController(mMockPipTransitionController); mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory); |