summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Merissa Mitchell <liyingtan@google.com> 2025-01-29 14:32:35 -0800
committer Merissa Mitchell <liyingtan@google.com> 2025-01-29 20:42:05 -0800
commit33b797b95f30b73d1ebb50fa23fa932c238eca3c (patch)
treee9f8aa3a05f7a97b9bde9c263b6e27cc086d21b4
parent0af50eb34063f95c8d820da0739aab1b03644935 (diff)
[PiP on Desktop] Add PipDesktopState for PiP checks in Desktop Mode.
This is a refactoring work to provide a centralized place to check PiP in Desktop Mode status. Bug: 350475854 Test: atest DesktopTasksTransitionObserverTest DesktopRepositoryTest DesktopTasksControllerTest PipSchedulerTest PipDesktopStateTest Test: Manually verify all PiP on Desktop CUJs are WAI. Flag: com.android.window.flags.enable_desktop_windowing_pip Change-Id: I06114e0bb4194fefa8a38cc2b4fc6c94c47f3907
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java159
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java57
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java204
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java21
6 files changed, 403 insertions, 145 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
new file mode 100644
index 000000000000..c10c2c905c97
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.app.ActivityManager;
+import android.window.DisplayAreaInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.window.flags.Flags;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+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.pip2.phone.PipTransition;
+
+import java.util.Optional;
+
+/** Helper class for PiP on Desktop Mode. */
+public class PipDesktopState {
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
+ private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
+ private final Optional<DesktopWallpaperActivityTokenProvider>
+ mDesktopWallpaperActivityTokenProviderOptional;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+
+ public PipDesktopState(PipDisplayLayoutState pipDisplayLayoutState,
+ Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
+ Optional<DesktopWallpaperActivityTokenProvider>
+ desktopWallpaperActivityTokenProviderOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+ mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
+ mDesktopWallpaperActivityTokenProviderOptional =
+ desktopWallpaperActivityTokenProviderOptional;
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ }
+
+ /**
+ * Returns whether PiP in Desktop Windowing is enabled by checking the following:
+ * - Desktop Windowing in PiP flag is enabled
+ * - DesktopWallpaperActivityTokenProvider is injected
+ * - DesktopUserRepositories is injected
+ */
+ public boolean isDesktopWindowingPipEnabled() {
+ return Flags.enableDesktopWindowingPip()
+ && mDesktopWallpaperActivityTokenProviderOptional.isPresent()
+ && mDesktopUserRepositoriesOptional.isPresent();
+ }
+
+ /** Returns whether PiP in Connected Displays is enabled by checking the flag. */
+ public boolean isConnectedDisplaysPipEnabled() {
+ return Flags.enableConnectedDisplaysPip();
+ }
+
+ /** Returns whether the display with the PiP task is in freeform windowing mode. */
+ private boolean isDisplayInFreeform() {
+ final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+ mPipDisplayLayoutState.getDisplayId());
+ if (tdaInfo != null) {
+ return tdaInfo.configuration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FREEFORM;
+ }
+ return false;
+ }
+
+ /** Returns whether PiP is exiting while we're in a Desktop Mode session. */
+ private boolean isPipExitingToDesktopMode() {
+ // Early return if PiP in Desktop Windowing is not supported.
+ if (!isDesktopWindowingPipEnabled()) {
+ return false;
+ }
+ final int displayId = mPipDisplayLayoutState.getDisplayId();
+ return getDesktopRepository().getVisibleTaskCount(displayId) > 0
+ || getDesktopWallpaperActivityTokenProvider().isWallpaperActivityVisible(displayId)
+ || isDisplayInFreeform();
+ }
+
+ /** Returns whether {@param pipTask} would be entering in a Desktop Mode session. */
+ public boolean isPipEnteringInDesktopMode(ActivityManager.RunningTaskInfo pipTask) {
+ // Early return if PiP in Desktop Windowing is not supported.
+ if (!isDesktopWindowingPipEnabled()) {
+ return false;
+ }
+ final DesktopRepository desktopRepository = getDesktopRepository();
+ return desktopRepository.getVisibleTaskCount(pipTask.getDisplayId()) > 0
+ || desktopRepository.isMinimizedPipPresentInDisplay(pipTask.getDisplayId());
+ }
+
+ /**
+ * Invoked when an EXIT_PiP transition is detected in {@link PipTransition}.
+ * Returns whether the PiP exiting should also trigger the active Desktop Mode session to exit.
+ */
+ public boolean shouldExitPipExitDesktopMode() {
+ // Early return if PiP in Desktop Windowing is not supported.
+ if (!isDesktopWindowingPipEnabled()) {
+ return false;
+ }
+ final int displayId = mPipDisplayLayoutState.getDisplayId();
+ return getDesktopRepository().getVisibleTaskCount(displayId) == 0
+ && getDesktopWallpaperActivityTokenProvider().isWallpaperActivityVisible(displayId);
+ }
+
+ /**
+ * Returns a {@link WindowContainerTransaction} that reorders the {@link WindowContainerToken}
+ * of the DesktopWallpaperActivity for the display with the given {@param displayId}.
+ */
+ public WindowContainerTransaction getWallpaperActivityTokenWct(int displayId) {
+ return new WindowContainerTransaction().reorder(
+ getDesktopWallpaperActivityTokenProvider().getToken(displayId), /* onTop= */ false);
+ }
+
+ /**
+ * The windowing mode to restore to when resizing out of PIP direction.
+ * Defaults to undefined and can be overridden to restore to an alternate windowing mode.
+ */
+ public int getOutPipWindowingMode() {
+ // If we are exiting PiP while the device is in Desktop mode (the task should expand to
+ // freeform windowing mode):
+ // 1) If the display windowing mode is freeform, set windowing mode to UNDEFINED so it will
+ // resolve the windowing mode to the display's windowing mode.
+ // 2) If the display windowing mode is not FREEFORM, set windowing mode to FREEFORM.
+ if (isPipExitingToDesktopMode()) {
+ if (isDisplayInFreeform()) {
+ return WINDOWING_MODE_UNDEFINED;
+ } else {
+ return WINDOWING_MODE_FREEFORM;
+ }
+ }
+
+ // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
+ return WINDOWING_MODE_UNDEFINED;
+ }
+
+ private DesktopRepository getDesktopRepository() {
+ return mDesktopUserRepositoriesOptional.get().getCurrent();
+ }
+
+ private DesktopWallpaperActivityTokenProvider getDesktopWallpaperActivityTokenProvider() {
+ return mDesktopWallpaperActivityTokenProviderOptional.get();
+ }
+}
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 413300612f7d..e7c76bbd91b2 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
@@ -31,6 +31,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.common.pip.PipPerfHintController;
@@ -84,14 +85,11 @@ public abstract class Pip2Module {
@NonNull PipDisplayLayoutState pipDisplayLayoutState,
@NonNull PipUiStateChangeController pipUiStateChangeController,
DisplayController displayController,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional) {
+ PipDesktopState pipDesktopState) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
pipScheduler, pipStackListenerController, pipDisplayLayoutState,
- pipUiStateChangeController, displayController, desktopUserRepositoriesOptional,
- desktopWallpaperActivityTokenProviderOptional);
+ pipUiStateChangeController, displayController, pipDesktopState);
}
@WMSingleton
@@ -142,13 +140,9 @@ public abstract class Pip2Module {
PipBoundsState pipBoundsState,
@ShellMainThread ShellExecutor mainExecutor,
PipTransitionState pipTransitionState,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ PipDesktopState pipDesktopState) {
return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState,
- desktopUserRepositoriesOptional, desktopWallpaperActivityTokenProviderOptional,
- rootTaskDisplayAreaOrganizer);
+ pipDesktopState);
}
@WMSingleton
@@ -233,4 +227,17 @@ public abstract class Pip2Module {
return new PipTaskListener(context, shellTaskOrganizer, pipTransitionState,
pipScheduler, pipBoundsState, pipBoundsAlgorithm, mainExecutor);
}
+
+ @WMSingleton
+ @Provides
+ static PipDesktopState providePipDesktopState(
+ PipDisplayLayoutState pipDisplayLayoutState,
+ Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
+ Optional<DesktopWallpaperActivityTokenProvider>
+ desktopWallpaperActivityTokenProviderOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
+ ) {
+ return new PipDesktopState(pipDisplayLayoutState, desktopUserRepositoriesOptional,
+ desktopWallpaperActivityTokenProviderOptional, rootTaskDisplayAreaOrganizer);
+ }
}
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 21b0820f523a..e17587ff18bc 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
@@ -16,14 +16,10 @@
package com.android.wm.shell.pip2.phone;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.SurfaceControl;
-import android.window.DisplayAreaInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -32,20 +28,14 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
-import com.android.window.flags.Flags;
-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.common.pip.PipDesktopState;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.Objects;
-import java.util.Optional;
-
/**
* Scheduler for Shell initiated PiP transitions and animations.
*/
@@ -56,10 +46,7 @@ public class PipScheduler {
private final PipBoundsState mPipBoundsState;
private final ShellExecutor mMainExecutor;
private final PipTransitionState mPipTransitionState;
- private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
- private final Optional<DesktopWallpaperActivityTokenProvider>
- mDesktopWallpaperActivityTokenProviderOptional;
- private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private final PipDesktopState mPipDesktopState;
private PipTransitionController mPipTransitionController;
private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
@@ -72,18 +59,12 @@ public class PipScheduler {
PipBoundsState pipBoundsState,
ShellExecutor mainExecutor,
PipTransitionState pipTransitionState,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ PipDesktopState pipDesktopState) {
mContext = context;
mPipBoundsState = pipBoundsState;
mMainExecutor = mainExecutor;
mPipTransitionState = pipTransitionState;
- mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
- mDesktopWallpaperActivityTokenProviderOptional =
- desktopWallpaperActivityTokenProviderOptional;
- mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ mPipDesktopState = pipDesktopState;
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
@@ -105,7 +86,7 @@ public class PipScheduler {
wct.setBounds(pipTaskToken, null);
// if we are hitting a multi-activity case
// windowing mode change will reparent to original host task
- wct.setWindowingMode(pipTaskToken, getOutPipWindowingMode());
+ wct.setWindowingMode(pipTaskToken, mPipDesktopState.getOutPipWindowingMode());
return wct;
}
@@ -235,55 +216,6 @@ public class PipScheduler {
maybeUpdateMovementBounds();
}
- /** Returns whether the display is in freeform windowing mode. */
- private boolean isDisplayInFreeform() {
- final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
- Objects.requireNonNull(mPipTransitionState.getPipTaskInfo()).displayId);
- if (tdaInfo != null) {
- return tdaInfo.configuration.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_FREEFORM;
- }
- return false;
- }
-
- /** Returns whether PiP is exiting while we're in desktop mode. */
- private boolean isPipExitingToDesktopMode() {
- // 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();
- }
-
- /**
- * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
- * and can be overridden to restore to an alternate windowing mode.
- */
- private int getOutPipWindowingMode() {
- // If we are exiting PiP while the device is in Desktop mode (the task should expand to
- // freeform windowing mode):
- // 1) If the display windowing mode is freeform, set windowing mode to undefined so it will
- // resolve the windowing mode to the display's windowing mode.
- // 2) If the display windowing mode is not freeform, set windowing mode to freeform.
- if (isPipExitingToDesktopMode()) {
- if (isDisplayInFreeform()) {
- return WINDOWING_MODE_UNDEFINED;
- } else {
- return WINDOWING_MODE_FREEFORM;
- }
- }
-
- // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
- return WINDOWING_MODE_UNDEFINED;
- }
-
@VisibleForTesting
void setSurfaceControlTransactionFactory(
@NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
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 229962488acf..d3e630ddc703 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
@@ -56,19 +56,16 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.internal.util.Preconditions;
-import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ComponentUtils;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDesktopState;
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.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;
@@ -79,8 +76,6 @@ import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import java.util.Optional;
-
/**
* Implementation of transitions for PiP on phone.
*/
@@ -117,9 +112,7 @@ public class PipTransition extends PipTransitionController implements
private final PipDisplayLayoutState mPipDisplayLayoutState;
private final DisplayController mDisplayController;
private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
- private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
- private final Optional<DesktopWallpaperActivityTokenProvider>
- mDesktopWallpaperActivityTokenProviderOptional;
+ private final PipDesktopState mPipDesktopState;
//
// Transition caches
@@ -159,9 +152,7 @@ public class PipTransition extends PipTransitionController implements
PipDisplayLayoutState pipDisplayLayoutState,
PipUiStateChangeController pipUiStateChangeController,
DisplayController displayController,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional) {
+ PipDesktopState pipDesktopState) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
@@ -174,9 +165,7 @@ public class PipTransition extends PipTransitionController implements
mPipDisplayLayoutState = pipDisplayLayoutState;
mDisplayController = displayController;
mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(mContext);
- mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
- mDesktopWallpaperActivityTokenProviderOptional =
- desktopWallpaperActivityTokenProviderOptional;
+ mPipDesktopState = pipDesktopState;
}
@Override
@@ -800,7 +789,7 @@ public class PipTransition extends PipTransitionController implements
&& getFixedRotationDelta(info, pipTaskChange) == ROTATION_90) {
adjustedSourceRectHint.offset(cutoutInsets.left, cutoutInsets.top);
}
- if (Flags.enableDesktopWindowingPip()) {
+ if (mPipDesktopState.isDesktopWindowingPipEnabled()) {
adjustedSourceRectHint.offset(-pipActivityChange.getStartAbsBounds().left,
-pipActivityChange.getStartAbsBounds().top);
}
@@ -860,7 +849,7 @@ public class PipTransition extends PipTransitionController implements
// If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct
// display info that PiP is entering in.
- if (Flags.enableConnectedDisplaysPip()) {
+ if (mPipDesktopState.isConnectedDisplaysPipEnabled()) {
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(
pipTask.displayId);
if (displayLayout != null) {
@@ -908,12 +897,7 @@ public class PipTransition extends PipTransitionController implements
// 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()
- .isMinimizedPipPresentInDisplay(pipTask.displayId));
- if (isInDesktopSession) {
+ if (mPipDesktopState.isPipEnteringInDesktopMode(pipTask)) {
return false;
}
@@ -1087,26 +1071,13 @@ 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
- );
- }
+ if (mPipDesktopState.shouldExitPipExitDesktopMode()) {
+ mTransitions.startTransition(
+ TRANSIT_TO_BACK,
+ mPipDesktopState.getWallpaperActivityTokenWct(
+ mPipTransitionState.getPipTaskInfo().getDisplayId()),
+ null /* firstHandler */
+ );
}
mPipTransitionState.setPinnedTaskLeash(null);
mPipTransitionState.setPipTaskInfo(null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
new file mode 100644
index 000000000000..75ad621e1cad
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.android.window.flags.Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.platform.test.annotations.EnableFlags;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.window.DisplayAreaInfo;
+import android.window.WindowContainerToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+/**
+ * Unit test against {@link PipDesktopState}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+public class PipDesktopStateTest {
+ @Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
+ @Mock private Optional<DesktopUserRepositories> mMockDesktopUserRepositoriesOptional;
+ @Mock private Optional<DesktopWallpaperActivityTokenProvider>
+ mMockDesktopWallpaperActivityTokenProviderOptional;
+ @Mock private DesktopUserRepositories mMockDesktopUserRepositories;
+ @Mock private DesktopWallpaperActivityTokenProvider mMockDesktopWallpaperActivityTokenProvider;
+ @Mock private DesktopRepository mMockDesktopRepository;
+ @Mock private RootTaskDisplayAreaOrganizer mMockRootTaskDisplayAreaOrganizer;
+ @Mock private ActivityManager.RunningTaskInfo mMockTaskInfo;
+
+ private static final int DISPLAY_ID = 1;
+ private DisplayAreaInfo mDefaultTda;
+ private PipDesktopState mPipDesktopState;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockDesktopUserRepositoriesOptional.get()).thenReturn(mMockDesktopUserRepositories);
+ when(mMockDesktopWallpaperActivityTokenProviderOptional.get()).thenReturn(
+ mMockDesktopWallpaperActivityTokenProvider);
+ when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mMockDesktopRepository);
+ when(mMockDesktopUserRepositoriesOptional.isPresent()).thenReturn(true);
+ when(mMockDesktopWallpaperActivityTokenProviderOptional.isPresent()).thenReturn(true);
+
+ when(mMockTaskInfo.getDisplayId()).thenReturn(DISPLAY_ID);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(DISPLAY_ID);
+
+ mDefaultTda = new DisplayAreaInfo(Mockito.mock(WindowContainerToken.class), DISPLAY_ID, 0);
+ when(mMockRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DISPLAY_ID)).thenReturn(
+ mDefaultTda);
+
+ mPipDesktopState = new PipDesktopState(mMockPipDisplayLayoutState,
+ mMockDesktopUserRepositoriesOptional,
+ mMockDesktopWallpaperActivityTokenProviderOptional,
+ mMockRootTaskDisplayAreaOrganizer);
+ }
+
+ @Test
+ public void isDesktopWindowingPipEnabled_returnsTrue() {
+ assertTrue(mPipDesktopState.isDesktopWindowingPipEnabled());
+ }
+
+ @Test
+ public void isDesktopWindowingPipEnabled_desktopRepositoryEmpty_returnsFalse() {
+ when(mMockDesktopUserRepositoriesOptional.isPresent()).thenReturn(false);
+
+ assertFalse(mPipDesktopState.isDesktopWindowingPipEnabled());
+ }
+
+ @Test
+ public void isDesktopWindowingPipEnabled_desktopWallpaperEmpty_returnsFalse() {
+ when(mMockDesktopWallpaperActivityTokenProviderOptional.isPresent()).thenReturn(false);
+
+ assertFalse(mPipDesktopState.isDesktopWindowingPipEnabled());
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CONNECTED_DISPLAYS_PIP)
+ public void isConnectedDisplaysPipEnabled_returnsTrue() {
+ assertTrue(mPipDesktopState.isConnectedDisplaysPipEnabled());
+ }
+
+ @Test
+ public void isPipEnteringInDesktopMode_visibleCountZero_minimizedPipPresent_returnsTrue() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
+ when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(true);
+
+ assertTrue(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
+ }
+
+ @Test
+ public void isPipEnteringInDesktopMode_visibleCountNonzero_minimizedPipAbsent_returnsTrue() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(1);
+ when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(false);
+
+ assertTrue(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
+ }
+
+ @Test
+ public void isPipEnteringInDesktopMode_visibleCountZero_minimizedPipAbsent_returnsFalse() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
+ when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(false);
+
+ assertFalse(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
+ }
+
+ @Test
+ public void shouldExitPipExitDesktopMode_visibleCountZero_wallpaperInvisible_returnsFalse() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
+ when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
+ DISPLAY_ID)).thenReturn(false);
+
+ assertFalse(mPipDesktopState.shouldExitPipExitDesktopMode());
+ }
+
+ @Test
+ public void shouldExitPipExitDesktopMode_visibleCountNonzero_wallpaperVisible_returnsFalse() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(1);
+ when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
+ DISPLAY_ID)).thenReturn(true);
+
+ assertFalse(mPipDesktopState.shouldExitPipExitDesktopMode());
+ }
+
+ @Test
+ public void shouldExitPipExitDesktopMode_visibleCountZero_wallpaperVisible_returnsTrue() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
+ when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
+ DISPLAY_ID)).thenReturn(true);
+
+ assertTrue(mPipDesktopState.shouldExitPipExitDesktopMode());
+ }
+
+ @Test
+ public void getOutPipWindowingMode_exitToDesktop_displayFreeform_returnsUndefined() {
+ // Set visible task count to 1 so isPipExitingToDesktopMode returns true
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(1);
+ setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ assertEquals(WINDOWING_MODE_UNDEFINED, mPipDesktopState.getOutPipWindowingMode());
+ }
+
+ @Test
+ public void getOutPipWindowingMode_exitToDesktop_displayFullscreen_returnsFreeform() {
+ // Set visible task count to 1 so isPipExitingToDesktopMode returns true
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(1);
+ setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ assertEquals(WINDOWING_MODE_FREEFORM, mPipDesktopState.getOutPipWindowingMode());
+ }
+
+ @Test
+ public void getOutPipWindowingMode_exitToFullscreen_displayFullscreen_returnsUndefined() {
+ setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ assertEquals(WINDOWING_MODE_UNDEFINED, mPipDesktopState.getOutPipWindowingMode());
+ }
+
+ private void setDisplayWindowingMode(int windowingMode) {
+ mDefaultTda.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ }
+}
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 bd857c7dcd45..8e0381e4f933 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
@@ -26,7 +26,6 @@ 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;
@@ -39,12 +38,9 @@ import android.window.WindowContainerTransaction;
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.common.pip.PipDesktopState;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
@@ -55,11 +51,8 @@ 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;
-
/**
* Unit test against {@link PipScheduler}
*/
@@ -77,16 +70,13 @@ public class PipSchedulerTest {
@Mock private PipBoundsState mMockPipBoundsState;
@Mock private ShellExecutor mMockMainExecutor;
@Mock private PipTransitionState mMockPipTransitionState;
+ @Mock private PipDesktopState mMockPipDesktopState;
@Mock private PipTransitionController mMockPipTransitionController;
@Mock private Runnable mMockUpdateMovementBoundsRunnable;
@Mock private WindowContainerToken mMockPipTaskToken;
@Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
@Mock private SurfaceControl.Transaction mMockTransaction;
@Mock private PipAlphaAnimator mMockAlphaAnimator;
- @Mock private DesktopUserRepositories mMockDesktopUserRepositories;
- @Mock private DesktopWallpaperActivityTokenProvider mMockDesktopWallpaperActivityTokenProvider;
- @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
-
@Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
@Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor;
@@ -101,14 +91,9 @@ 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, Optional.of(mMockDesktopUserRepositories),
- Optional.of(mMockDesktopWallpaperActivityTokenProvider),
- mRootTaskDisplayAreaOrganizer);
+ mMockPipTransitionState, mMockPipDesktopState);
mPipScheduler.setPipTransitionController(mMockPipTransitionController);
mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory);
mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, startTx, finishTx, direction) ->