summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/desktopwallpaperactivity/DesktopWallpaperActivityTokenProvider.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java38
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt62
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt117
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt130
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java13
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);