summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml31
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml8
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml3
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml3
-rw-r--r--libs/WindowManager/Shell/res/values/styles.xml1
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt164
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt97
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java35
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt391
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt98
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt98
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt74
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt35
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt94
32 files changed, 1176 insertions, 439 deletions
diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
index 4daaf9c6b57f..225303b2d942 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
@@ -103,4 +103,35 @@
</LinearLayout>
+ <!-- Menu option to move a bubble to fullscreen; only visible if bubble anything is enabled. -->
+ <LinearLayout
+ android:id="@+id/bubble_manage_menu_fullscreen_container"
+ android:background="@drawable/bubble_manage_menu_row"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:minHeight="@dimen/bubble_menu_item_height"
+ android:gravity="center_vertical"
+ android:paddingStart="@dimen/bubble_menu_padding"
+ android:paddingEnd="@dimen/bubble_menu_padding"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/bubble_manage_menu_fullscreen_icon"
+ android:layout_width="@dimen/bubble_menu_icon_size"
+ android:layout_height="@dimen/bubble_menu_icon_size"
+ android:src="@drawable/desktop_mode_ic_handle_menu_fullscreen"
+ android:tint="@color/bubbles_icon_tint"/>
+
+ <TextView
+ android:id="@+id/bubble_manage_menu_fullscreen_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:text="@string/bubble_fullscreen_text"
+ android:textColor="@androidprv:color/materialColorOnSurface"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+ </LinearLayout>
+
</LinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index ed8b54397076..bfaa40771894 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -43,8 +43,7 @@
android:layout_height="@dimen/desktop_mode_handle_menu_icon_radius"
android:layout_marginStart="10dp"
android:layout_marginEnd="12dp"
- android:contentDescription="@string/app_icon_text"
- android:importantForAccessibility="no"/>
+ android:contentDescription="@string/app_icon_text" />
<com.android.wm.shell.windowdecor.MarqueedTextView
android:id="@+id/application_name"
@@ -142,7 +141,6 @@
android:contentDescription="@string/screenshot_text"
android:text="@string/screenshot_text"
android:src="@drawable/desktop_mode_ic_handle_menu_screenshot"
- android:importantForAccessibility="no"
style="@style/DesktopModeHandleMenuActionButton"/>
<com.android.wm.shell.windowdecor.HandleMenuActionButton
@@ -150,7 +148,6 @@
android:contentDescription="@string/new_window_text"
android:text="@string/new_window_text"
android:src="@drawable/desktop_mode_ic_handle_menu_new_window"
- android:importantForAccessibility="no"
style="@style/DesktopModeHandleMenuActionButton"/>
<com.android.wm.shell.windowdecor.HandleMenuActionButton
@@ -158,7 +155,6 @@
android:contentDescription="@string/manage_windows_text"
android:text="@string/manage_windows_text"
android:src="@drawable/desktop_mode_ic_handle_menu_manage_windows"
- android:importantForAccessibility="no"
style="@style/DesktopModeHandleMenuActionButton"/>
<com.android.wm.shell.windowdecor.HandleMenuActionButton
@@ -166,7 +162,6 @@
android:contentDescription="@string/change_aspect_ratio_text"
android:text="@string/change_aspect_ratio_text"
android:src="@drawable/desktop_mode_ic_handle_menu_change_aspect_ratio"
- android:importantForAccessibility="no"
style="@style/DesktopModeHandleMenuActionButton"/>
</LinearLayout>
@@ -186,7 +181,6 @@
android:text="@string/open_in_browser_text"
android:src="@drawable/desktop_mode_ic_handle_menu_open_in_browser"
style="@style/DesktopModeHandleMenuActionButton"
- android:importantForAccessibility="no"
android:layout_width="0dp"
android:layout_weight="1"/>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml
index de38e6fc2330..35e7de0e7c1e 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu_action_button.xml
@@ -22,8 +22,7 @@
android:layout_height="match_parent"
android:gravity="start|center_vertical"
android:paddingHorizontal="16dp"
- android:clickable="true"
- android:focusable="true"
+ android:importantForAccessibility="yes"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground">
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 2a8c88ed3a40..e1bf6638a9b2 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -291,6 +291,9 @@
<!-- Corner radius for expanded view drop target -->
<dimen name="bubble_bar_expanded_view_drop_target_corner">28dp</dimen>
<dimen name="bubble_bar_expanded_view_drop_target_padding">24dp</dimen>
+ <dimen name="bubble_bar_expanded_view_drop_target_padding_top">60dp</dimen>
+ <dimen name="bubble_bar_expanded_view_drop_target_padding_bottom">24dp</dimen>
+ <dimen name="bubble_bar_expanded_view_drop_target_padding_horizontal">48dp</dimen>
<!-- Width of the box around bottom center of the screen where drag only leads to dismiss -->
<dimen name="bubble_bar_dismiss_zone_width">192dp</dimen>
<!-- Height of the box around bottom center of the screen where drag only leads to dismiss -->
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 637b47ab3ace..5f1db83d7acb 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -45,6 +45,7 @@
<item name="android:layout_height">52dp</item>
<item name="android:textColor">@androidprv:color/materialColorOnSurface</item>
<item name="android:drawableTint">@androidprv:color/materialColorOnSurface</item>
+ <item name="android:importantForAccessibility">no</item>
</style>
<style name="DesktopModeHandleMenuActionButtonImage">
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
index e145ea9dfa8f..529203f7ded2 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicy.kt
@@ -49,15 +49,21 @@ class DesktopModeCompatPolicy(private val context: Context) {
*/
fun isTopActivityExemptFromDesktopWindowing(task: TaskInfo) =
isTopActivityExemptFromDesktopWindowing(task.baseActivity?.packageName,
- task.numActivities, task.isTopActivityNoDisplay, task.isActivityStackTransparent)
+ task.numActivities, task.isTopActivityNoDisplay, task.isActivityStackTransparent,
+ task.userId)
- fun isTopActivityExemptFromDesktopWindowing(packageName: String?,
- numActivities: Int, isTopActivityNoDisplay: Boolean, isActivityStackTransparent: Boolean) =
+ fun isTopActivityExemptFromDesktopWindowing(
+ packageName: String?,
+ numActivities: Int,
+ isTopActivityNoDisplay: Boolean,
+ isActivityStackTransparent: Boolean,
+ userId: Int
+ ) =
DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue &&
((isSystemUiTask(packageName) ||
isPartOfDefaultHomePackageOrNoHomeAvailable(packageName) ||
(isTransparentTask(isActivityStackTransparent, numActivities) &&
- hasFullscreenTransparentPermission(packageName))) &&
+ hasFullscreenTransparentPermission(packageName, userId))) &&
!isTopActivityNoDisplay)
/** @see DesktopModeCompatUtils.shouldExcludeCaptionFromAppBounds */
@@ -80,16 +86,17 @@ class DesktopModeCompatPolicy(private val context: Context) {
private fun isSystemUiTask(packageName: String?) = packageName == systemUiPackage
// Checks if the app for the given package has the SYSTEM_ALERT_WINDOW permission.
- private fun hasFullscreenTransparentPermission(packageName: String?): Boolean {
+ private fun hasFullscreenTransparentPermission(packageName: String?, userId: Int): Boolean {
if (DesktopModeFlags.ENABLE_MODALS_FULLSCREEN_WITH_PERMISSIONS.isTrue) {
if (packageName == null) {
return false
}
- return packageInfoCache.getOrPut(packageName) {
+ return packageInfoCache.getOrPut("$userId@$packageName") {
try {
- val packageInfo = pkgManager.getPackageInfo(
+ val packageInfo = pkgManager.getPackageInfoAsUser(
packageName,
- PackageManager.GET_PERMISSIONS
+ PackageManager.GET_PERMISSIONS,
+ userId
)
packageInfo?.requestedPermissions?.contains(SYSTEM_ALERT_WINDOW) == true
} catch (e: PackageManager.NameNotFoundException) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 8ac9230c36c3..cbd1e9671825 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -606,6 +606,10 @@ public class BubbleExpandedView extends LinearLayout {
updateManageButtonIfExists();
}
+ public float getCornerRadius() {
+ return mCornerRadius;
+ }
+
/**
* Updates the size and visuals of the pointer if {@link #mPointerView} is initialized.
* Does nothing otherwise.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 70340d7032b4..03d6b0a8075d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -103,7 +103,9 @@ public class BubblePositioner implements BubbleDropTargetBoundsProvider {
private int mManageButtonHeight;
private int mOverflowHeight;
private int mMinimumFlyoutWidthLargeScreen;
- private int mBubbleBarExpandedViewDropTargetPadding;
+ private int mBarExpViewDropTargetPaddingTop;
+ private int mBarExpViewDropTargetPaddingBottom;
+ private int mBarExpViewDropTargetPaddingHorizontal;
private PointF mRestingStackPosition;
@@ -173,8 +175,12 @@ public class BubblePositioner implements BubbleDropTargetBoundsProvider {
res.getDimensionPixelSize(R.dimen.bubble_bar_expanded_view_width),
mPositionRect.width() - 2 * mExpandedViewPadding
);
- mBubbleBarExpandedViewDropTargetPadding = res.getDimensionPixelSize(
- R.dimen.bubble_bar_expanded_view_drop_target_padding);
+ mBarExpViewDropTargetPaddingTop = res.getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_drop_target_padding_top);
+ mBarExpViewDropTargetPaddingBottom = res.getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_drop_target_padding_bottom);
+ mBarExpViewDropTargetPaddingHorizontal = res.getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_drop_target_padding_horizontal);
if (mShowingInBubbleBar) {
mExpandedViewLargeScreenWidth = mExpandedViewBubbleBarWidth;
@@ -986,8 +992,15 @@ public class BubblePositioner implements BubbleDropTargetBoundsProvider {
public Rect getBubbleBarExpandedViewDropTargetBounds(boolean onLeft) {
Rect bounds = new Rect();
getBubbleBarExpandedViewBounds(onLeft, false, bounds);
- bounds.inset(mBubbleBarExpandedViewDropTargetPadding,
- mBubbleBarExpandedViewDropTargetPadding);
+ // Drop target bounds are based on expanded view bounds with some padding added
+ int leftPadding = onLeft ? 0 : mBarExpViewDropTargetPaddingHorizontal;
+ int rightPadding = onLeft ? mBarExpViewDropTargetPaddingHorizontal : 0;
+ bounds.inset(
+ leftPadding,
+ mBarExpViewDropTargetPaddingTop,
+ rightPadding,
+ mBarExpViewDropTargetPaddingBottom
+ );
return bounds;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 92724178cf84..dd5a23aae7f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -91,6 +91,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.DeviceConfig;
import com.android.wm.shell.shared.bubbles.DismissView;
import com.android.wm.shell.shared.bubbles.RelativeTouchListener;
@@ -1319,7 +1320,7 @@ public class BubbleStackView extends FrameLayout
mBubbleContainer.bringToFront();
}
- // TODO: Create ManageMenuView and move setup / animations there
+ // TODO (b/402196554) : Create ManageMenuView and move setup / animations there
private void setUpManageMenu() {
if (mManageMenu != null) {
removeView(mManageMenu);
@@ -1377,6 +1378,22 @@ public class BubbleStackView extends FrameLayout
mManageSettingsIcon = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_icon);
mManageSettingsText = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_name);
+ View fullscreenView = mManageMenu.findViewById(
+ R.id.bubble_manage_menu_fullscreen_container);
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+ fullscreenView.setVisibility(VISIBLE);
+ fullscreenView.setOnClickListener(
+ view -> {
+ showManageMenu(false /* show */);
+ BubbleExpandedView expandedView = getExpandedView();
+ if (expandedView != null && expandedView.getTaskView() != null) {
+ expandedView.getTaskView().moveToFullscreen();
+ }
+ });
+ } else {
+ fullscreenView.setVisibility(GONE);
+ }
+
// The menu itself should respect locale direction so the icons are on the correct side.
mManageMenu.setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
addView(mManageMenu);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
index 8cd6ce020408..c1841c707a2f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTransitions.java
@@ -612,8 +612,7 @@ public class BubbleTransitions {
mTaskLeash = taskChg.getLeash();
mRootLeash = info.getRoot(0).getLeash();
- SurfaceControl dest =
- mBubble.getBubbleBarExpandedView().getViewRootImpl().getSurfaceControl();
+ SurfaceControl dest = getExpandedView(mBubble).getViewRootImpl().getSurfaceControl();
final Runnable onPlucked = () -> {
// Need to remove the taskview AFTER applying the startTransaction because
// it isn't synchronized.
@@ -623,12 +622,12 @@ public class BubbleTransitions {
mBubbleData.setExpanded(false /* expanded */);
};
if (dest != null) {
- pluck(mTaskLeash, mBubble.getBubbleBarExpandedView(), dest,
+ pluck(mTaskLeash, getExpandedView(mBubble), dest,
taskChg.getStartAbsBounds().left - info.getRoot(0).getOffset().x,
taskChg.getStartAbsBounds().top - info.getRoot(0).getOffset().y,
- mBubble.getBubbleBarExpandedView().getCornerRadius(), startTransaction,
+ getCornerRadius(mBubble), startTransaction,
onPlucked);
- mBubble.getBubbleBarExpandedView().post(() -> mTransitions.dispatchTransition(
+ getExpandedView(mBubble).post(() -> mTransitions.dispatchTransition(
mTransition, info, startTransaction, finishTransaction, finishCallback,
null));
} else {
@@ -649,6 +648,20 @@ public class BubbleTransitions {
t.reparent(mTaskLeash, mRootLeash);
t.apply();
}
+
+ private View getExpandedView(@NonNull Bubble bubble) {
+ if (bubble.getBubbleBarExpandedView() != null) {
+ return bubble.getBubbleBarExpandedView();
+ }
+ return bubble.getExpandedView();
+ }
+
+ private float getCornerRadius(@NonNull Bubble bubble) {
+ if (bubble.getBubbleBarExpandedView() != null) {
+ return bubble.getBubbleBarExpandedView().getCornerRadius();
+ }
+ return bubble.getExpandedView().getCornerRadius();
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
index b7761ec75782..69009fd1606a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -28,9 +28,9 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.app.animation.Interpolators;
-import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubble;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import java.util.ArrayList;
@@ -263,7 +263,7 @@ class BubbleBarMenuViewController {
}
));
- if (Flags.enableBubbleAnything() || Flags.enableBubbleToFullscreen()) {
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
menuActions.add(new BubbleBarMenuView.MenuAction(
Icon.createWithResource(resources,
R.drawable.desktop_mode_ic_handle_menu_fullscreen),
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 bc2ed3f35b45..67a4d6cf89bf 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
@@ -98,6 +98,7 @@ import com.android.wm.shell.desktopmode.DesktopModeKeyGestureHandler;
import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopModeMoveToDisplayTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
+import com.android.wm.shell.desktopmode.DesktopPipTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopTaskChangeListener;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
@@ -780,6 +781,7 @@ public abstract class WMShellModule {
OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
DesksOrganizer desksOrganizer,
Optional<DesksTransitionObserver> desksTransitionObserver,
+ Optional<DesktopPipTransitionObserver> desktopPipTransitionObserver,
UserProfileContexts userProfileContexts,
DesktopModeCompatPolicy desktopModeCompatPolicy,
DragToDisplayTransitionHandler dragToDisplayTransitionHandler,
@@ -823,6 +825,7 @@ public abstract class WMShellModule {
overviewToDesktopTransitionObserver,
desksOrganizer,
desksTransitionObserver.get(),
+ desktopPipTransitionObserver,
userProfileContexts,
desktopModeCompatPolicy,
dragToDisplayTransitionHandler,
@@ -1225,6 +1228,7 @@ public abstract class WMShellModule {
Transitions transitions,
ShellTaskOrganizer shellTaskOrganizer,
Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
+ Optional<DesktopPipTransitionObserver> desktopPipTransitionObserver,
Optional<BackAnimationController> backAnimationController,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
ShellInit shellInit) {
@@ -1237,6 +1241,7 @@ public abstract class WMShellModule {
transitions,
shellTaskOrganizer,
desktopMixedTransitionHandler.get(),
+ desktopPipTransitionObserver,
backAnimationController.get(),
desktopWallpaperActivityTokenProvider,
shellInit)));
@@ -1258,6 +1263,19 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
+ static Optional<DesktopPipTransitionObserver> provideDesktopPipTransitionObserver(
+ Context context
+ ) {
+ if (DesktopModeStatus.canEnterDesktopMode(context)
+ && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue()) {
+ return Optional.of(
+ new DesktopPipTransitionObserver());
+ }
+ return Optional.empty();
+ }
+
+ @WMSingleton
+ @Provides
static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
Context context,
Transitions transitions,
@@ -1474,6 +1492,7 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
InputManager inputManager,
+ DisplayController displayController,
@ShellMainThread Handler mainHandler
) {
if (!DesktopModeStatus.canEnterDesktopMode(context)) {
@@ -1488,6 +1507,7 @@ public abstract class WMShellModule {
shellTaskOrganizer,
desktopWallpaperActivityTokenProvider,
inputManager,
+ displayController,
mainHandler));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
index 904d86282c39..489e4f0aed01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
@@ -35,9 +35,11 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.transition.Transitions
/** Controls the display windowing mode in desktop mode */
@@ -49,6 +51,7 @@ class DesktopDisplayModeController(
private val shellTaskOrganizer: ShellTaskOrganizer,
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
private val inputManager: InputManager,
+ private val displayController: DisplayController,
@ShellMainThread private val mainHandler: Handler,
) {
@@ -111,37 +114,70 @@ class DesktopDisplayModeController(
transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
}
+ // Do not directly use this method to check the state of desktop-first mode. Check the display
+ // windowing mode instead.
+ private fun canDesktopFirstModeBeEnabledOnDefaultDisplay(): Boolean {
+ if (isDefaultDisplayDesktopEligible()) {
+ if (isExtendedDisplayEnabled() && hasExternalDisplay()) {
+ return true
+ }
+ if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) {
+ if (isInClamshellMode()) {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
@VisibleForTesting
fun getTargetWindowingModeForDefaultDisplay(): Int {
- if (isExtendedDisplayEnabled() && hasExternalDisplay()) {
+ if (canDesktopFirstModeBeEnabledOnDefaultDisplay()) {
return WINDOWING_MODE_FREEFORM
}
- if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) {
- if (isInClamshellMode()) {
- return WINDOWING_MODE_FREEFORM
- }
- return WINDOWING_MODE_FULLSCREEN
- }
- // If form factor-based desktop first switch is disabled, use the default display windowing
- // mode here to keep the freeform mode for some form factors (e.g., FEATURE_PC).
- return windowManager.getWindowingMode(DEFAULT_DISPLAY)
+ return if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) {
+ WINDOWING_MODE_FULLSCREEN
+ } else {
+ // If form factor-based desktop first switch is disabled, use the default display
+ // windowing mode here to keep the freeform mode for some form factors (e.g.,
+ // FEATURE_PC).
+ windowManager.getWindowingMode(DEFAULT_DISPLAY)
+ }
}
- // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
- private fun isExtendedDisplayEnabled() =
- 0 !=
+ private fun isExtendedDisplayEnabled(): Boolean {
+ if (DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue) {
+ return rootTaskDisplayAreaOrganizer
+ .getDisplayIds()
+ .filter { it != DEFAULT_DISPLAY }
+ .any { displayId ->
+ displayController.getDisplay(displayId)?.let { display ->
+ DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, display)
+ } ?: false
+ }
+ }
+
+ return 0 !=
Settings.Global.getInt(
context.contentResolver,
DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
0,
)
+ }
private fun hasExternalDisplay() =
rootTaskDisplayAreaOrganizer.getDisplayIds().any { it != DEFAULT_DISPLAY }
private fun isInClamshellMode() = inputManager.isInTabletMode() == InputManager.SWITCH_STATE_OFF
+ private fun isDefaultDisplayDesktopEligible(): Boolean {
+ val display = requireNotNull(displayController.getDisplay(DEFAULT_DISPLAY)) {
+ "Display object of DEFAULT_DISPLAY must be non-null."
+ }
+ return DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, display)
+ }
+
private fun logV(msg: String, vararg arguments: Any?) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt
new file mode 100644
index 000000000000..efd3866e1bc4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserver.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.desktopmode
+
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.os.IBinder
+import android.window.DesktopModeFlags
+import android.window.TransitionInfo
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+
+/**
+ * Observer of PiP in Desktop Mode transitions. At the moment, this is specifically tracking a PiP
+ * transition for a task that is entering PiP via the minimize button on the caption bar.
+ */
+class DesktopPipTransitionObserver {
+ private val pendingPipTransitions = mutableMapOf<IBinder, PendingPipTransition>()
+
+ /** Adds a pending PiP transition to be tracked. */
+ fun addPendingPipTransition(transition: PendingPipTransition) {
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue) return
+ pendingPipTransitions[transition.token] = transition
+ }
+
+ /**
+ * Called when any transition is ready, which may include transitions not tracked by this
+ * observer.
+ */
+ fun onTransitionReady(transition: IBinder, info: TransitionInfo) {
+ if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue) return
+ val pipTransition = pendingPipTransitions.remove(transition) ?: return
+
+ logD("Desktop PiP transition ready: %s", transition)
+ for (change in info.changes) {
+ val taskInfo = change.taskInfo
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ continue
+ }
+
+ if (
+ taskInfo.taskId == pipTransition.taskId &&
+ taskInfo.windowingMode == WINDOWING_MODE_PINNED
+ ) {
+ logD("Desktop PiP transition was successful")
+ pipTransition.onSuccess()
+ return
+ }
+ }
+ logD("Change with PiP task not found in Desktop PiP transition; likely failed")
+ }
+
+ /**
+ * Data tracked for a pending PiP transition.
+ *
+ * @property token the PiP transition that is started.
+ * @property taskId task id of the task entering PiP.
+ * @property onSuccess callback to be invoked if the PiP transition is successful.
+ */
+ data class PendingPipTransition(val token: IBinder, val taskId: Int, val onSuccess: () -> Unit)
+
+ private fun logD(msg: String, vararg arguments: Any?) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ private companion object {
+ private const val TAG = "DesktopPipTransitionObserver"
+ }
+}
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 a4e9c52ac9d9..6cb26b54e802 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
@@ -68,7 +68,6 @@ class DesktopRepository(
* @property topTransparentFullscreenTaskId the task id of any current top transparent
* fullscreen task launched on top of the desk. 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.
*/
private data class Desk(
val deskId: Int,
@@ -81,7 +80,6 @@ class DesktopRepository(
val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
var fullImmersiveTaskId: Int? = null,
var topTransparentFullscreenTaskId: Int? = null,
- var pipTaskId: Int? = null,
) {
fun deepCopy(): Desk =
Desk(
@@ -94,7 +92,6 @@ class DesktopRepository(
freeformTasksInZOrder = ArrayList(freeformTasksInZOrder),
fullImmersiveTaskId = fullImmersiveTaskId,
topTransparentFullscreenTaskId = topTransparentFullscreenTaskId,
- pipTaskId = pipTaskId,
)
// TODO: b/362720497 - remove when multi-desktops is enabled where instances aren't
@@ -107,7 +104,6 @@ class DesktopRepository(
freeformTasksInZOrder.clear()
fullImmersiveTaskId = null
topTransparentFullscreenTaskId = null
- pipTaskId = null
}
}
@@ -127,9 +123,6 @@ 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
@@ -611,57 +604,6 @@ class DesktopRepository(
}
/**
- * Set whether the given task is the Desktop-entered PiP task in this display's active desk.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun setTaskInPip(displayId: Int, taskId: Int, enterPip: Boolean) {
- val activeDesk =
- desktopData.getActiveDesk(displayId)
- ?: error("Expected active desk in display: $displayId")
- if (enterPip) {
- activeDesk.pipTaskId = taskId
- } else {
- activeDesk.pipTaskId =
- if (activeDesk.pipTaskId == taskId) null
- else {
- logW(
- "setTaskInPip: taskId=%d did not match saved taskId=%d",
- taskId,
- activeDesk.pipTaskId,
- )
- activeDesk.pipTaskId
- }
- }
- }
-
- /**
- * Returns whether the given task is the Desktop-entered PiP task in this display's active desk.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun isTaskMinimizedPipInDisplay(displayId: Int, taskId: Int): Boolean =
- desktopData.getActiveDesk(displayId)?.pipTaskId == taskId
-
- /**
- * Saves callback to handle a pending PiP transition being aborted.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun setOnPipAbortedCallback(callbackIfPipAborted: ((displayId: Int, pipTaskId: Int) -> Unit)?) {
- onPipAbortedCallback = callbackIfPipAborted
- }
-
- /**
- * Invokes callback to handle a pending PiP transition with the given task id being aborted.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun onPipAborted(displayId: Int, pipTaskId: Int) {
- onPipAbortedCallback?.invoke(displayId, pipTaskId)
- }
-
- /**
* Set whether the given task is the full-immersive task in this display's active desk.
*
* TODO: b/389960283 - consider forcing callers to use [setTaskInFullImmersiveStateInDesk] with
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 5e9cd9016d92..50f5beb4166a 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
@@ -214,6 +214,7 @@ class DesktopTasksController(
private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver,
private val desksOrganizer: DesksOrganizer,
private val desksTransitionObserver: DesksTransitionObserver,
+ private val desktopPipTransitionObserver: Optional<DesktopPipTransitionObserver>,
private val userProfileContexts: UserProfileContexts,
private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
private val dragToDisplayTransitionHandler: DragToDisplayTransitionHandler,
@@ -793,10 +794,31 @@ class DesktopTasksController(
fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
val wct = WindowContainerTransaction()
-
+ val taskId = taskInfo.taskId
+ val displayId = taskInfo.displayId
+ val deskId =
+ taskRepository.getDeskIdForTask(taskInfo.taskId)
+ ?: if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ logW("minimizeTask: desk not found for task: ${taskInfo.taskId}")
+ return
+ } else {
+ getDefaultDeskId(taskInfo.displayId)
+ }
+ val isLastTask =
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ taskRepository.isOnlyVisibleNonClosingTaskInDesk(
+ taskId = taskId,
+ deskId = checkNotNull(deskId) { "Expected non-null deskId" },
+ displayId = displayId,
+ )
+ } else {
+ taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId)
+ }
val isMinimizingToPip =
DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue &&
- (taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false)
+ desktopPipTransitionObserver.isPresent &&
+ (taskInfo.pictureInPictureParams?.isAutoEnterEnabled ?: false)
+
// If task is going to PiP, start a PiP transition instead of a minimize transition
if (isMinimizingToPip) {
val requestInfo =
@@ -810,75 +832,60 @@ class DesktopTasksController(
)
val requestRes = transitions.dispatchRequest(Binder(), requestInfo, /* skip= */ null)
wct.merge(requestRes.second, true)
- freeformTaskTransitionStarter.startPipTransition(wct)
- taskRepository.setTaskInPip(taskInfo.displayId, taskInfo.taskId, enterPip = true)
- taskRepository.setOnPipAbortedCallback { displayId, taskId ->
- minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!, minimizeReason)
- taskRepository.setTaskInPip(displayId, taskId, enterPip = false)
- }
- return
- }
-
- minimizeTaskInner(taskInfo, minimizeReason)
- }
- private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) {
- val taskId = taskInfo.taskId
- val deskId = taskRepository.getDeskIdForTask(taskInfo.taskId)
- if (deskId == null && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- logW("minimizeTaskInner: desk not found for task: ${taskInfo.taskId}")
- return
- }
- val displayId = taskInfo.displayId
- val wct = WindowContainerTransaction()
-
- snapEventHandler.removeTaskIfTiled(displayId, taskId)
- val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false)
- val desktopExitRunnable =
- performDesktopExitCleanUp(
- wct = wct,
- deskId = deskId,
- displayId = displayId,
- willExitDesktop = willExitDesktop,
- )
- // Notify immersive handler as it might need to exit immersive state.
- val exitResult =
- desktopImmersiveController.exitImmersiveIfApplicable(
- wct = wct,
- taskInfo = taskInfo,
- reason = DesktopImmersiveController.ExitReason.MINIMIZED,
- )
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- desksOrganizer.minimizeTask(
- wct = wct,
- deskId = checkNotNull(deskId) { "Expected non-null deskId" },
- task = taskInfo,
+ desktopPipTransitionObserver.get().addPendingPipTransition(
+ DesktopPipTransitionObserver.PendingPipTransition(
+ token = freeformTaskTransitionStarter.startPipTransition(wct),
+ taskId = taskInfo.taskId,
+ onSuccess = {
+ onDesktopTaskEnteredPip(
+ taskId = taskId,
+ deskId = deskId,
+ displayId = taskInfo.displayId,
+ taskIsLastVisibleTaskBeforePip = isLastTask,
+ )
+ },
+ )
)
} else {
- wct.reorder(taskInfo.token, /* onTop= */ false)
- }
- val isLastTask =
+ snapEventHandler.removeTaskIfTiled(displayId, taskId)
+ val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false)
+ val desktopExitRunnable =
+ performDesktopExitCleanUp(
+ wct = wct,
+ deskId = deskId,
+ displayId = displayId,
+ willExitDesktop = willExitDesktop,
+ )
+ // Notify immersive handler as it might need to exit immersive state.
+ val exitResult =
+ desktopImmersiveController.exitImmersiveIfApplicable(
+ wct = wct,
+ taskInfo = taskInfo,
+ reason = DesktopImmersiveController.ExitReason.MINIMIZED,
+ )
if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- taskRepository.isOnlyVisibleNonClosingTaskInDesk(
- taskId = taskId,
+ desksOrganizer.minimizeTask(
+ wct = wct,
deskId = checkNotNull(deskId) { "Expected non-null deskId" },
- displayId = displayId,
+ task = taskInfo,
)
} else {
- taskRepository.isOnlyVisibleNonClosingTask(taskId = taskId, displayId = displayId)
+ wct.reorder(taskInfo.token, /* onTop= */ false)
}
- val transition =
- freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask)
- desktopTasksLimiter.ifPresent {
- it.addPendingMinimizeChange(
- transition = transition,
- displayId = displayId,
- taskId = taskId,
- minimizeReason = minimizeReason,
- )
+ val transition =
+ freeformTaskTransitionStarter.startMinimizedModeTransition(wct, taskId, isLastTask)
+ desktopTasksLimiter.ifPresent {
+ it.addPendingMinimizeChange(
+ transition = transition,
+ displayId = displayId,
+ taskId = taskId,
+ minimizeReason = minimizeReason,
+ )
+ }
+ exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
+ desktopExitRunnable?.invoke(transition)
}
- exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
- desktopExitRunnable?.invoke(transition)
}
/** Move a task with given `taskId` to fullscreen */
@@ -1845,7 +1852,11 @@ class DesktopTasksController(
displayId: Int,
forceExitDesktop: Boolean,
): Boolean {
- if (forceExitDesktop && DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ if (
+ forceExitDesktop &&
+ (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue ||
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue)
+ ) {
// |forceExitDesktop| is true when the callers knows we'll exit desktop, such as when
// explicitly going fullscreen, so there's no point in checking the desktop state.
return true
@@ -1862,6 +1873,33 @@ class DesktopTasksController(
return true
}
+ /** Potentially perform Desktop cleanup after a task successfully enters PiP. */
+ @VisibleForTesting
+ fun onDesktopTaskEnteredPip(
+ taskId: Int,
+ deskId: Int,
+ displayId: Int,
+ taskIsLastVisibleTaskBeforePip: Boolean,
+ ) {
+ if (
+ !willExitDesktop(taskId, displayId, forceExitDesktop = taskIsLastVisibleTaskBeforePip)
+ ) {
+ return
+ }
+
+ val wct = WindowContainerTransaction()
+ val desktopExitRunnable =
+ performDesktopExitCleanUp(
+ wct = wct,
+ deskId = deskId,
+ displayId = displayId,
+ willExitDesktop = true,
+ )
+
+ val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+ desktopExitRunnable?.invoke(transition)
+ }
+
private fun performDesktopExitCleanupIfNeeded(
taskId: Int,
deskId: Int? = null,
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 7dabeb7c9d15..df4d18f8c803 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
@@ -23,7 +23,6 @@ import android.os.IBinder
import android.view.SurfaceControl
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.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
@@ -38,11 +37,12 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktop
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.TransitionUtil.isClosingMode
+import com.android.wm.shell.shared.TransitionUtil.isOpeningMode
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 java.util.Optional
/**
* A [Transitions.TransitionObserver] that observes shell transitions and updates the
@@ -55,6 +55,7 @@ class DesktopTasksTransitionObserver(
private val transitions: Transitions,
private val shellTaskOrganizer: ShellTaskOrganizer,
private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
+ private val desktopPipTransitionObserver: Optional<DesktopPipTransitionObserver>,
private val backAnimationController: BackAnimationController,
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
shellInit: ShellInit,
@@ -63,8 +64,6 @@ class DesktopTasksTransitionObserver(
data class CloseWallpaperTransition(val transition: IBinder, val displayId: Int)
private var transitionToCloseWallpaper: CloseWallpaperTransition? = 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 {
@@ -98,33 +97,7 @@ class DesktopTasksTransitionObserver(
removeTaskIfNeeded(info)
}
removeWallpaperOnLastTaskClosingIfNeeded(transition, info)
-
- val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
- info.changes.forEach { change ->
- change.taskInfo?.let { taskInfo ->
- if (
- DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue &&
- 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,
- )
- }
- }
- }
- }
+ desktopPipTransitionObserver.ifPresent { it.onTransitionReady(transition, info) }
}
private fun removeTaskIfNeeded(info: TransitionInfo) {
@@ -299,18 +272,6 @@ 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
}
}
@@ -345,18 +306,29 @@ class DesktopTasksTransitionObserver(
}
private fun updateTopTransparentFullscreenTaskId(info: TransitionInfo) {
- info.changes.forEach { change ->
- change.taskInfo?.let { task ->
- val desktopRepository = desktopUserRepositories.getProfile(task.userId)
- val displayId = task.displayId
- // Clear `topTransparentFullscreenTask` information from repository if task
- // is closed or sent to back.
- if (
- TransitionUtil.isClosingMode(change.mode) &&
- task.taskId ==
- desktopRepository.getTopTransparentFullscreenTaskId(displayId)
- ) {
- desktopRepository.clearTopTransparentFullscreenTaskId(displayId)
+ run forEachLoop@{
+ info.changes.forEach { change ->
+ change.taskInfo?.let { task ->
+ val desktopRepository = desktopUserRepositories.getProfile(task.userId)
+ val displayId = task.displayId
+ val transparentTaskId =
+ desktopRepository.getTopTransparentFullscreenTaskId(displayId)
+ if (transparentTaskId == null) return@forEachLoop
+ val changeMode = change.mode
+ val taskId = task.taskId
+ val isTopTransparentFullscreenTaskClosing =
+ taskId == transparentTaskId && isClosingMode(changeMode)
+ val isNonTopTransparentFullscreenTaskOpening =
+ taskId != transparentTaskId && isOpeningMode(changeMode)
+ // Clear `topTransparentFullscreenTask` information from repository if task
+ // is closed, sent to back or if a different task is opened, brought to front.
+ if (
+ isTopTransparentFullscreenTaskClosing ||
+ isNonTopTransparentFullscreenTaskOpening
+ ) {
+ desktopRepository.clearTopTransparentFullscreenTaskId(displayId)
+ return@forEachLoop
+ }
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 003baae29114..bcf9396ff0c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -553,7 +553,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return;
}
- if (oldRootView != mResult.mRootView) {
+ if (DesktopModeFlags.SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX.isTrue()
+ ? (oldRootView != mResult.mRootView && taskInfo.isVisibleRequested)
+ : oldRootView != mResult.mRootView) {
disposeStatusBarInputLayer();
mWindowDecorViewHolder = createViewHolder();
// Load these only when first creating the view.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 7a4a834e9dc2..0b86d1dbbc58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -23,12 +23,12 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERL
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
+import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
-import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.isEdgeResizePermitted;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.isEventFromTouchscreen;
@@ -127,7 +127,9 @@ class DragResizeInputListener implements AutoCloseable {
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
DisplayController displayController,
- DesktopModeEventLogger desktopModeEventLogger) {
+ DesktopModeEventLogger desktopModeEventLogger,
+ InputChannel inputChannel,
+ InputChannel sinkInputChannel) {
mContext = context;
mWindowSession = windowSession;
mBgExecutor = bgExecutor;
@@ -136,7 +138,11 @@ class DragResizeInputListener implements AutoCloseable {
mHandler = handler;
mChoreographer = choreographer;
mDisplayId = displayId;
- mDecorationSurface = decorationSurface;
+ // Creates a new SurfaceControl pointing the same underlying surface with decorationSurface
+ // to ensure that mDecorationSurface will not be released while it's used on the background
+ // thread. Note that the empty name will be overridden by the next copyFrom call.
+ mDecorationSurface = surfaceControlBuilderSupplier.get().setName("").build();
+ mDecorationSurface.copyFrom(decorationSurface, "DragResizeInputListener");
mDragPositioningCallback = callback;
mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
@@ -154,9 +160,13 @@ class DragResizeInputListener implements AutoCloseable {
final InputSetUpResult result = setUpInputChannels(mDisplayId, mWindowSession,
mDecorationSurface, mClientToken, mSinkClientToken,
mSurfaceControlBuilderSupplier,
- mSurfaceControlTransactionSupplier);
+ mSurfaceControlTransactionSupplier, inputChannel, sinkInputChannel);
mainExecutor.execute(() -> {
if (mClosed) {
+ result.mInputChannel.dispose();
+ result.mSinkInputChannel.dispose();
+ mSurfaceControlTransactionSupplier.get().remove(
+ result.mInputSinkSurface).apply();
return;
}
mInputSinkSurface = result.mInputSinkSurface;
@@ -208,7 +218,7 @@ class DragResizeInputListener implements AutoCloseable {
new DefaultTaskResizeInputEventReceiverFactory(), taskInfo,
handler, choreographer, displayId, decorationSurface, callback,
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
- displayController, desktopModeEventLogger);
+ displayController, desktopModeEventLogger, new InputChannel(), new InputChannel());
}
DragResizeInputListener(
@@ -251,11 +261,11 @@ class DragResizeInputListener implements AutoCloseable {
@NonNull IBinder clientToken,
@NonNull IBinder sinkClientToken,
@NonNull Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
- @NonNull Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
+ @NonNull Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
+ @NonNull InputChannel inputChannel,
+ @NonNull InputChannel sinkInputChannel) {
Trace.beginSection("DragResizeInputListener#setUpInputChannels");
final InputTransferToken inputTransferToken = new InputTransferToken();
- final InputChannel inputChannel = new InputChannel();
- final InputChannel sinkInputChannel = new InputChannel();
try {
windowSession.grantInputChannel(
displayId,
@@ -421,6 +431,9 @@ class DragResizeInputListener implements AutoCloseable {
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
+ // Removing this surface on the background thread to ensure that mInitInputChannels has
+ // already been finished.
+ mSurfaceControlTransactionSupplier.get().remove(mDecorationSurface).apply();
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index a8a7032d0b86..9cc64ac9c276 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -109,15 +109,18 @@ class HandleMenu(
get() = (DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue() && !taskInfo.isFreeform)
private val pillTopMargin: Int = loadDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
+ R.dimen.desktop_mode_handle_menu_pill_spacing_margin
+ )
private val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_width)
private val menuHeight = getHandleMenuHeight()
private val marginMenuTop = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_margin_top)
private val marginMenuStart = loadDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_margin_start)
+ R.dimen.desktop_mode_handle_menu_margin_start
+ )
@VisibleForTesting
var handleMenuViewContainer: AdditionalViewContainer? = null
+
@VisibleForTesting
var handleMenuView: HandleMenuView? = null
@@ -136,7 +139,7 @@ class HandleMenu(
private val shouldShowMoreActionsPill: Boolean
get() = SHOULD_SHOW_SCREENSHOT_BUTTON || shouldShowNewWindowButton ||
- shouldShowManageWindowsButton || shouldShowChangeAspectRatioButton
+ shouldShowManageWindowsButton || shouldShowChangeAspectRatioButton
private var loadAppInfoJob: Job? = null
@@ -240,7 +243,8 @@ class HandleMenu(
val y = handleMenuPosition.y.toInt()
handleMenuViewContainer =
if ((!taskInfo.isFreeform && DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue())
- || forceShowSystemBars) {
+ || forceShowSystemBars
+ ) {
AdditionalSystemViewContainer(
windowManagerWrapper = windowManagerWrapper,
taskId = taskInfo.taskId,
@@ -251,7 +255,11 @@ class HandleMenu(
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
view = handleMenuView.rootView,
- forciblyShownTypes = if (forceShowSystemBars) { systemBars() } else { 0 },
+ forciblyShownTypes = if (forceShowSystemBars) {
+ systemBars()
+ } else {
+ 0
+ },
ignoreCutouts = Flags.showAppHandleLargeScreens()
|| BubbleAnythingFlagHelper.enableBubbleToFullscreen()
)
@@ -369,7 +377,8 @@ class HandleMenu(
inputPoint.y - globalMenuPosition.y
)
if (splitScreenController.getSplitPosition(taskInfo.taskId)
- == SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT) {
+ == SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+ ) {
val leftStageBounds = Rect()
splitScreenController.getStageBounds(leftStageBounds, Rect())
inputRelativeToMenu.x += leftStageBounds.width().toFloat()
@@ -398,7 +407,8 @@ class HandleMenu(
var menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_handle_menu_height)
if (!shouldShowWindowingPill) {
menuHeight -= loadDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_windowing_pill_height)
+ R.dimen.desktop_mode_handle_menu_windowing_pill_height
+ )
menuHeight -= pillTopMargin
}
if (!SHOULD_SHOW_SCREENSHOT_BUTTON) {
@@ -418,14 +428,16 @@ class HandleMenu(
}
if (!shouldShowChangeAspectRatioButton) {
menuHeight -= loadDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_change_aspect_ratio_height)
+ R.dimen.desktop_mode_handle_menu_change_aspect_ratio_height
+ )
}
if (!shouldShowMoreActionsPill) {
menuHeight -= pillTopMargin
}
if (!shouldShowBrowserPill) {
menuHeight -= loadDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height)
+ R.dimen.desktop_mode_handle_menu_open_in_browser_pill_height
+ )
menuHeight -= pillTopMargin
}
return menuHeight
@@ -468,48 +480,66 @@ class HandleMenu(
// Insets for ripple effect of App Info Pill. and Windowing Pill. buttons
val iconButtondrawableShiftInset = context.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_icon_button_ripple_inset_shift)
+ R.dimen.desktop_mode_handle_menu_icon_button_ripple_inset_shift
+ )
val iconButtondrawableBaseInset = context.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_icon_button_ripple_inset_base)
+ R.dimen.desktop_mode_handle_menu_icon_button_ripple_inset_base
+ )
private val iconButtonRippleRadius = context.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_handle_menu_icon_button_ripple_radius)
- private val iconButtonDrawableInsetsBase = DrawableInsets(t = iconButtondrawableBaseInset,
+ R.dimen.desktop_mode_handle_menu_icon_button_ripple_radius
+ )
+ private val iconButtonDrawableInsetsBase = DrawableInsets(
+ t = iconButtondrawableBaseInset,
b = iconButtondrawableBaseInset, l = iconButtondrawableBaseInset,
- r = iconButtondrawableBaseInset)
- private val iconButtonDrawableInsetsLeft = DrawableInsets(t = iconButtondrawableBaseInset,
- b = iconButtondrawableBaseInset, l = iconButtondrawableShiftInset, r = 0)
- private val iconButtonDrawableInsetsRight = DrawableInsets(t = iconButtondrawableBaseInset,
- b = iconButtondrawableBaseInset, l = 0, r = iconButtondrawableShiftInset)
+ r = iconButtondrawableBaseInset
+ )
+ private val iconButtonDrawableInsetsLeft = DrawableInsets(
+ t = iconButtondrawableBaseInset,
+ b = iconButtondrawableBaseInset, l = iconButtondrawableShiftInset, r = 0
+ )
+ private val iconButtonDrawableInsetsRight = DrawableInsets(
+ t = iconButtondrawableBaseInset,
+ b = iconButtondrawableBaseInset, l = 0, r = iconButtondrawableShiftInset
+ )
// App Info Pill.
private val appInfoPill = rootView.requireViewById<View>(R.id.app_info_pill)
private val collapseMenuButton = appInfoPill.requireViewById<HandleMenuImageButton>(
- R.id.collapse_menu_button)
+ R.id.collapse_menu_button
+ )
+
@VisibleForTesting
val appIconView = appInfoPill.requireViewById<ImageView>(R.id.application_icon)
+
@VisibleForTesting
val appNameView = appInfoPill.requireViewById<MarqueedTextView>(R.id.application_name)
// Windowing Pill.
private val windowingPill = rootView.requireViewById<View>(R.id.windowing_pill)
private val fullscreenBtn = windowingPill.requireViewById<ImageButton>(
- R.id.fullscreen_button)
+ R.id.fullscreen_button
+ )
private val splitscreenBtn = windowingPill.requireViewById<ImageButton>(
- R.id.split_screen_button)
+ R.id.split_screen_button
+ )
private val floatingBtn = windowingPill.requireViewById<ImageButton>(R.id.floating_button)
private val floatingBtnSpace = windowingPill.requireViewById<Space>(
- R.id.floating_button_space)
+ R.id.floating_button_space
+ )
private val desktopBtn = windowingPill.requireViewById<ImageButton>(R.id.desktop_button)
private val desktopBtnSpace = windowingPill.requireViewById<Space>(
- R.id.desktop_button_space)
+ R.id.desktop_button_space
+ )
// More Actions Pill.
private val moreActionsPill = rootView.requireViewById<View>(R.id.more_actions_pill)
private val screenshotBtn = moreActionsPill.requireViewById<HandleMenuActionButton>(
- R.id.screenshot_button)
+ R.id.screenshot_button
+ )
private val newWindowBtn = moreActionsPill.requireViewById<HandleMenuActionButton>(
- R.id.new_window_button)
+ R.id.new_window_button
+ )
private val manageWindowBtn = moreActionsPill
.requireViewById<HandleMenuActionButton>(R.id.manage_windows_button)
private val changeAspectRatioBtn = moreActionsPill
@@ -517,11 +547,14 @@ class HandleMenu(
// Open in Browser/App Pill.
private val openInAppOrBrowserPill = rootView.requireViewById<View>(
- R.id.open_in_app_or_browser_pill)
+ R.id.open_in_app_or_browser_pill
+ )
private val openInAppOrBrowserBtn = openInAppOrBrowserPill.requireViewById<View>(
- R.id.open_in_app_or_browser_button)
+ R.id.open_in_app_or_browser_button
+ )
private val openByDefaultBtn = openInAppOrBrowserPill.requireViewById<ImageButton>(
- R.id.open_by_default_button)
+ R.id.open_by_default_button
+ )
private val decorThemeUtil = DecorThemeUtil(context)
private val animator = HandleMenuAnimator(rootView, menuWidth, captionHeight.toFloat())
@@ -730,9 +763,9 @@ class HandleMenu(
desktopBtn.imageTintList = style.windowingButtonColor
val startInsets = if (context.isRtl) iconButtonDrawableInsetsRight
- else iconButtonDrawableInsetsLeft
+ else iconButtonDrawableInsetsLeft
val endInsets = if (context.isRtl) iconButtonDrawableInsetsLeft
- else iconButtonDrawableInsetsRight
+ else iconButtonDrawableInsetsRight
fullscreenBtn.apply {
background = createBackgroundDrawable(
@@ -804,9 +837,11 @@ class HandleMenu(
getString(R.string.open_in_browser_text)
}
+ val buttonRoot = openInAppOrBrowserBtn.requireViewById<LinearLayout>(R.id.action_button)
val label = openInAppOrBrowserBtn.requireViewById<MarqueedTextView>(R.id.label)
val image = openInAppOrBrowserBtn.requireViewById<ImageView>(R.id.image)
openInAppOrBrowserBtn.contentDescription = btnText
+ buttonRoot.contentDescription = btnText
label.apply {
text = btnText
setTextColor(style.textColor)
@@ -837,7 +872,7 @@ class HandleMenu(
*/
fun shouldShowChangeAspectRatioButton(taskInfo: RunningTaskInfo): Boolean =
taskInfo.appCompatTaskInfo.eligibleForUserAspectRatioButton() &&
- taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+ taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt
index 4b2e473d6ec2..a723a7a4ac20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuActionButton.kt
@@ -23,9 +23,7 @@ import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.ImageView
import android.widget.LinearLayout
-import android.widget.TextView
import androidx.core.content.withStyledAttributes
-import androidx.core.view.isGone
import com.android.wm.shell.R
/**
@@ -54,6 +52,7 @@ class HandleMenuActionButton @JvmOverloads constructor(
context.withStyledAttributes(attrs, R.styleable.HandleMenuActionButton) {
textView.text = getString(R.styleable.HandleMenuActionButton_android_text)
+ rootElement.contentDescription = getString(R.styleable.HandleMenuActionButton_android_text)
textView.setTextColor(getColor(R.styleable.HandleMenuActionButton_android_textColor, 0))
iconView.setImageResource(getResourceId(
R.styleable.HandleMenuActionButton_android_src, 0))
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index 9c55f0ecda93..7c5f34f979cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -152,6 +152,8 @@ class DesktopTilingWindowDecoration(
endBounds = destinationBounds,
callback,
)
+ } else {
+ callback.invoke()
}
}
return isTiled
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 05750a54f566..b139c000a1a9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -889,8 +889,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
*/
private void doStartEvents(int startX, int moveX) {
doMotionEvent(MotionEvent.ACTION_DOWN, startX);
- mController.onThresholdCrossed();
doMotionEvent(MotionEvent.ACTION_MOVE, moveX);
+ mController.onThresholdCrossed();
}
private void simulateRemoteAnimationStart() throws RemoteException {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
index 25f17fe8a3c2..b136bed3c942 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTransitionsTest.java
@@ -311,4 +311,39 @@ public class BubbleTransitionsTest extends ShellTestCase {
verify(startT).apply();
assertFalse(mTaskViewTransitions.hasPending());
}
+
+ @Test
+ public void convertFloatingBubbleToFullscreen() {
+ final BubbleExpandedView bev = mock(BubbleExpandedView.class);
+ final ViewRootImpl vri = mock(ViewRootImpl.class);
+ when(bev.getViewRootImpl()).thenReturn(vri);
+ when(mBubble.getBubbleBarExpandedView()).thenReturn(null);
+ when(mBubble.getExpandedView()).thenReturn(bev);
+
+ ActivityManager.RunningTaskInfo taskInfo = setupBubble();
+ final BubbleTransitions.BubbleTransition bt = mBubbleTransitions.startConvertFromBubble(
+ mBubble, taskInfo);
+ final BubbleTransitions.ConvertFromBubble cfb = (BubbleTransitions.ConvertFromBubble) bt;
+ verify(mTransitions).startTransition(anyInt(), any(), eq(cfb));
+ verify(mBubble).setPreparingTransition(eq(bt));
+ assertTrue(mTaskViewTransitions.hasPending());
+
+ final TransitionInfo info = new TransitionInfo(TRANSIT_CHANGE, 0);
+ final TransitionInfo.Change chg = new TransitionInfo.Change(taskInfo.token,
+ mock(SurfaceControl.class));
+ chg.setMode(TRANSIT_CHANGE);
+ chg.setTaskInfo(taskInfo);
+ info.addChange(chg);
+ info.addRoot(new TransitionInfo.Root(0, mock(SurfaceControl.class), 0, 0));
+ SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ Transitions.TransitionFinishCallback finishCb = wct -> {};
+ cfb.startAnimation(cfb.mTransition, info, startT, finishT, finishCb);
+
+ // Can really only verify that it interfaces with the taskViewTransitions queue.
+ // The actual functioning of this is tightly-coupled with SurfaceFlinger and renderthread
+ // in order to properly synchronize surface manipulation with drawing and thus can't be
+ // directly tested.
+ assertFalse(mTaskViewTransitions.hasPending());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
index 450989dd334d..da6a67c679ff 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
@@ -26,28 +26,36 @@ import android.os.Binder
import android.os.Handler
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.UsesFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.IWindowManager
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.DisplayAreaInfo
import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.server.display.feature.flags.Flags as DisplayFlags
import com.android.window.flags.Flags
import com.android.wm.shell.MockToken
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import com.google.testing.junit.testparameterinjector.TestParameterValuesProvider
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -60,6 +68,7 @@ import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
/**
* Test class for [DesktopDisplayModeController]
@@ -68,6 +77,7 @@ import org.mockito.kotlin.whenever
*/
@SmallTest
@RunWith(TestParameterInjector::class)
+@UsesFlags(com.android.server.display.feature.flags.Flags::class)
class DesktopDisplayModeControllerTest(
@TestParameter(valuesProvider = FlagsParameterizationProvider::class)
flags: FlagsParameterization
@@ -79,6 +89,7 @@ class DesktopDisplayModeControllerTest(
private val desktopWallpaperActivityTokenProvider =
mock<DesktopWallpaperActivityTokenProvider>()
private val inputManager = mock<InputManager>()
+ private val displayController = mock<DisplayController>()
private val mainHandler = mock<Handler>()
private lateinit var controller: DesktopDisplayModeController
@@ -90,6 +101,13 @@ class DesktopDisplayModeControllerTest(
TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build()
private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
private val wallpaperToken = MockToken().token()
+ private val defaultDisplay = mock<Display>()
+ private val externalDisplay = mock<Display>()
+
+ private lateinit var extendedDisplaySettingsRestoreSession:
+ ExtendedDisplaySettingsRestoreSession
+
+ private lateinit var mockitoSession: StaticMockitoSession
init {
mSetFlagsRule.setFlagsParameterization(flags)
@@ -97,6 +115,13 @@ class DesktopDisplayModeControllerTest(
@Before
fun setUp() {
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ extendedDisplaySettingsRestoreSession =
+ ExtendedDisplaySettingsRestoreSession(context.contentResolver)
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenReturn(Binder())
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
.thenReturn(defaultTDA)
@@ -109,42 +134,57 @@ class DesktopDisplayModeControllerTest(
shellTaskOrganizer,
desktopWallpaperActivityTokenProvider,
inputManager,
+ displayController,
mainHandler,
)
runningTasks.add(freeformTask)
runningTasks.add(fullscreenTask)
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(ArrayList(runningTasks))
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
+ whenever(displayController.getDisplay(DEFAULT_DISPLAY)).thenReturn(defaultDisplay)
+ whenever(displayController.getDisplay(EXTERNAL_DISPLAY_ID)).thenReturn(externalDisplay)
setTabletModeStatus(SwitchState.UNKNOWN)
+ whenever(
+ DesktopModeStatus.isDesktopModeSupportedOnDisplay(
+ context,
+ defaultDisplay
+ )
+ ).thenReturn(true)
+ }
+
+ @After
+ fun tearDown() {
+ extendedDisplaySettingsRestoreSession.restore()
+ mockitoSession.finishMocking()
}
private fun testDisplayWindowingModeSwitchOnDisplayConnected(expectToSwitch: Boolean) {
defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN)
- ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
- connectExternalDisplay()
- if (expectToSwitch) {
- // Assumes [connectExternalDisplay] properly triggered the switching transition.
- // Will verify the transition later along with [disconnectExternalDisplay].
- defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
- }
- disconnectExternalDisplay()
+ setExtendedMode(true)
+
+ connectExternalDisplay()
+ if (expectToSwitch) {
+ // Assumes [connectExternalDisplay] properly triggered the switching transition.
+ // Will verify the transition later along with [disconnectExternalDisplay].
+ defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ }
+ disconnectExternalDisplay()
- if (expectToSwitch) {
- val arg = argumentCaptor<WindowContainerTransaction>()
- verify(transitions, times(2))
- .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
- assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
- assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
- assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
- } else {
- verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
- }
+ if (expectToSwitch) {
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(2))
+ .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ } else {
+ verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
}
}
@@ -176,15 +216,16 @@ class DesktopDisplayModeControllerTest(
disconnectExternalDisplay()
}
setTabletModeStatus(tabletModeStatus)
-
- ExtendedDisplaySettingsSession(
- context.contentResolver,
- if (param.extendedDisplayEnabled) 1 else 0,
+ setExtendedMode(param.extendedDisplayEnabled)
+ whenever(
+ DesktopModeStatus.isDesktopModeSupportedOnDisplay(
+ context,
+ defaultDisplay
)
- .use {
- assertThat(controller.getTargetWindowingModeForDefaultDisplay())
- .isEqualTo(param.expectedWindowingMode)
- }
+ ).thenReturn(param.isDefaultDisplayDesktopEligible)
+
+ assertThat(controller.getTargetWindowingModeForDefaultDisplay())
+ .isEqualTo(param.expectedWindowingMode)
}
@Test
@@ -199,15 +240,16 @@ class DesktopDisplayModeControllerTest(
disconnectExternalDisplay()
}
setTabletModeStatus(param.tabletModeStatus)
-
- ExtendedDisplaySettingsSession(
- context.contentResolver,
- if (param.extendedDisplayEnabled) 1 else 0,
+ setExtendedMode(param.extendedDisplayEnabled)
+ whenever(
+ DesktopModeStatus.isDesktopModeSupportedOnDisplay(
+ context,
+ defaultDisplay
)
- .use {
- assertThat(controller.getTargetWindowingModeForDefaultDisplay())
- .isEqualTo(param.expectedWindowingMode)
- }
+ ).thenReturn(param.isDefaultDisplayDesktopEligible)
+
+ assertThat(controller.getTargetWindowingModeForDefaultDisplay())
+ .isEqualTo(param.expectedWindowingMode)
}
@Test
@@ -215,18 +257,16 @@ class DesktopDisplayModeControllerTest(
fun displayWindowingModeSwitch_existingTasksOnConnected() {
defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN)
+ setExtendedMode(true)
- ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
- connectExternalDisplay()
+ connectExternalDisplay()
- val arg = argumentCaptor<WindowContainerTransaction>()
- verify(transitions, times(1))
- .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
- assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
- }
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(1)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
}
@Test
@@ -236,18 +276,16 @@ class DesktopDisplayModeControllerTest(
whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
WINDOWING_MODE_FULLSCREEN
}
+ setExtendedMode(true)
- ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
- disconnectExternalDisplay()
+ disconnectExternalDisplay()
- val arg = argumentCaptor<WindowContainerTransaction>()
- verify(transitions, times(1))
- .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
- assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- }
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(1)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
}
private fun connectExternalDisplay() {
@@ -266,18 +304,30 @@ class DesktopDisplayModeControllerTest(
whenever(inputManager.isInTabletMode()).thenReturn(status.value)
}
- private class ExtendedDisplaySettingsSession(
- private val contentResolver: ContentResolver,
- private val overrideValue: Int,
- ) : AutoCloseable {
+ private fun setExtendedMode(enabled: Boolean) {
+ if (DisplayFlags.enableDisplayContentModeManagement()) {
+ whenever(
+ DesktopModeStatus.isDesktopModeSupportedOnDisplay(
+ context,
+ externalDisplay
+ )
+ ).thenReturn(enabled)
+ } else {
+ Settings.Global.putInt(
+ context.contentResolver,
+ DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
+ if (enabled) 1 else 0,
+ )
+ }
+ }
+
+ private class ExtendedDisplaySettingsRestoreSession(
+ private val contentResolver: ContentResolver
+ ) {
private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
- init {
- Settings.Global.putInt(contentResolver, settingName, overrideValue)
- }
-
- override fun close() {
+ fun restore() {
Settings.Global.putInt(contentResolver, settingName, initialValue)
}
}
@@ -287,7 +337,8 @@ class DesktopDisplayModeControllerTest(
context: TestParameterValuesProvider.Context
): List<FlagsParameterization> {
return FlagsParameterization.allCombinationsOf(
- Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH
+ Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH,
+ DisplayFlags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT,
)
}
}
@@ -305,54 +356,119 @@ class DesktopDisplayModeControllerTest(
val defaultWindowingMode: Int,
val hasExternalDisplay: Boolean,
val extendedDisplayEnabled: Boolean,
+ val isDefaultDisplayDesktopEligible: Boolean,
val expectedWindowingMode: Int,
) {
- FREEFORM_EXTERNAL_EXTENDED(
+ FREEFORM_EXTERNAL_EXTENDED_NO_PROJECTED(
defaultWindowingMode = WINDOWING_MODE_FREEFORM,
hasExternalDisplay = true,
extendedDisplayEnabled = true,
+ isDefaultDisplayDesktopEligible = true,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ ),
+ FULLSCREEN_EXTERNAL_EXTENDED_NO_PROJECTED(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = true,
+ isDefaultDisplayDesktopEligible = true,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ ),
+ FREEFORM_NO_EXTERNAL_EXTENDED_NO_PROJECTED(
+ defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = true,
+ isDefaultDisplayDesktopEligible = true,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ ),
+ FULLSCREEN_NO_EXTERNAL_EXTENDED_NO_PROJECTED(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = true,
+ isDefaultDisplayDesktopEligible = true,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ FREEFORM_EXTERNAL_MIRROR_NO_PROJECTED(
+ defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = false,
+ isDefaultDisplayDesktopEligible = true,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ ),
+ FULLSCREEN_EXTERNAL_MIRROR_NO_PROJECTED(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = false,
+ isDefaultDisplayDesktopEligible = true,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ FREEFORM_NO_EXTERNAL_MIRROR_NO_PROJECTED(
+ defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = false,
+ isDefaultDisplayDesktopEligible = true,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- FULLSCREEN_EXTERNAL_EXTENDED(
+ FULLSCREEN_NO_EXTERNAL_MIRROR_NO_PROJECTED(
defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = false,
+ isDefaultDisplayDesktopEligible = true,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ FREEFORM_EXTERNAL_EXTENDED_PROJECTED(
+ defaultWindowingMode = WINDOWING_MODE_FREEFORM,
hasExternalDisplay = true,
extendedDisplayEnabled = true,
+ isDefaultDisplayDesktopEligible = false,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- FREEFORM_NO_EXTERNAL_EXTENDED(
+ FULLSCREEN_EXTERNAL_EXTENDED_PROJECTED(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = true,
+ isDefaultDisplayDesktopEligible = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ FREEFORM_NO_EXTERNAL_EXTENDED_PROJECTED(
defaultWindowingMode = WINDOWING_MODE_FREEFORM,
hasExternalDisplay = false,
extendedDisplayEnabled = true,
+ isDefaultDisplayDesktopEligible = false,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- FULLSCREEN_NO_EXTERNAL_EXTENDED(
+ FULLSCREEN_NO_EXTERNAL_EXTENDED_PROJECTED(
defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
hasExternalDisplay = false,
extendedDisplayEnabled = true,
+ isDefaultDisplayDesktopEligible = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- FREEFORM_EXTERNAL_MIRROR(
+ FREEFORM_EXTERNAL_MIRROR_PROJECTED(
defaultWindowingMode = WINDOWING_MODE_FREEFORM,
hasExternalDisplay = true,
extendedDisplayEnabled = false,
+ isDefaultDisplayDesktopEligible = false,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- FULLSCREEN_EXTERNAL_MIRROR(
+ FULLSCREEN_EXTERNAL_MIRROR_PROJECTED(
defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
hasExternalDisplay = true,
extendedDisplayEnabled = false,
+ isDefaultDisplayDesktopEligible = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- FREEFORM_NO_EXTERNAL_MIRROR(
+ FREEFORM_NO_EXTERNAL_MIRROR_PROJECTED(
defaultWindowingMode = WINDOWING_MODE_FREEFORM,
hasExternalDisplay = false,
extendedDisplayEnabled = false,
+ isDefaultDisplayDesktopEligible = false,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- FULLSCREEN_NO_EXTERNAL_MIRROR(
+ FULLSCREEN_NO_EXTERNAL_MIRROR_PROJECTED(
defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
hasExternalDisplay = false,
extendedDisplayEnabled = false,
+ isDefaultDisplayDesktopEligible = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
}
@@ -361,78 +477,175 @@ class DesktopDisplayModeControllerTest(
val hasExternalDisplay: Boolean,
val extendedDisplayEnabled: Boolean,
val tabletModeStatus: SwitchState,
+ val isDefaultDisplayDesktopEligible: Boolean,
val expectedWindowingMode: Int,
) {
- EXTERNAL_EXTENDED_TABLET(
+ EXTERNAL_EXTENDED_TABLET_NO_PROJECTED(
hasExternalDisplay = true,
extendedDisplayEnabled = true,
tabletModeStatus = SwitchState.ON,
+ isDefaultDisplayDesktopEligible = true,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- NO_EXTERNAL_EXTENDED_TABLET(
+ NO_EXTERNAL_EXTENDED_TABLET_NO_PROJECTED(
hasExternalDisplay = false,
extendedDisplayEnabled = true,
tabletModeStatus = SwitchState.ON,
+ isDefaultDisplayDesktopEligible = true,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_MIRROR_TABLET(
+ EXTERNAL_MIRROR_TABLET_NO_PROJECTED(
hasExternalDisplay = true,
extendedDisplayEnabled = false,
tabletModeStatus = SwitchState.ON,
+ isDefaultDisplayDesktopEligible = true,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- NO_EXTERNAL_MIRROR_TABLET(
+ NO_EXTERNAL_MIRROR_TABLET_NO_PROJECTED(
hasExternalDisplay = false,
extendedDisplayEnabled = false,
tabletModeStatus = SwitchState.ON,
+ isDefaultDisplayDesktopEligible = true,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_EXTENDED_CLAMSHELL(
+ EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED(
hasExternalDisplay = true,
extendedDisplayEnabled = true,
tabletModeStatus = SwitchState.OFF,
+ isDefaultDisplayDesktopEligible = true,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- NO_EXTERNAL_EXTENDED_CLAMSHELL(
+ NO_EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED(
hasExternalDisplay = false,
extendedDisplayEnabled = true,
tabletModeStatus = SwitchState.OFF,
+ isDefaultDisplayDesktopEligible = true,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- EXTERNAL_MIRROR_CLAMSHELL(
+ EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED(
hasExternalDisplay = true,
extendedDisplayEnabled = false,
tabletModeStatus = SwitchState.OFF,
+ isDefaultDisplayDesktopEligible = true,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- NO_EXTERNAL_MIRROR_CLAMSHELL(
+ NO_EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED(
hasExternalDisplay = false,
extendedDisplayEnabled = false,
tabletModeStatus = SwitchState.OFF,
+ isDefaultDisplayDesktopEligible = true,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- EXTERNAL_EXTENDED_UNKNOWN(
+ EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED(
hasExternalDisplay = true,
extendedDisplayEnabled = true,
tabletModeStatus = SwitchState.UNKNOWN,
+ isDefaultDisplayDesktopEligible = true,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- NO_EXTERNAL_EXTENDED_UNKNOWN(
+ NO_EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED(
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = true,
+ tabletModeStatus = SwitchState.UNKNOWN,
+ isDefaultDisplayDesktopEligible = true,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED(
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = false,
+ tabletModeStatus = SwitchState.UNKNOWN,
+ isDefaultDisplayDesktopEligible = true,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ NO_EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED(
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = false,
+ tabletModeStatus = SwitchState.UNKNOWN,
+ isDefaultDisplayDesktopEligible = true,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ EXTERNAL_EXTENDED_TABLET_PROJECTED(
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = true,
+ tabletModeStatus = SwitchState.ON,
+ isDefaultDisplayDesktopEligible = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ NO_EXTERNAL_EXTENDED_TABLET_PROJECTED(
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = true,
+ tabletModeStatus = SwitchState.ON,
+ isDefaultDisplayDesktopEligible = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ EXTERNAL_MIRROR_TABLET_PROJECTED(
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = false,
+ tabletModeStatus = SwitchState.ON,
+ isDefaultDisplayDesktopEligible = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ NO_EXTERNAL_MIRROR_TABLET_PROJECTED(
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = false,
+ tabletModeStatus = SwitchState.ON,
+ isDefaultDisplayDesktopEligible = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED(
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = true,
+ tabletModeStatus = SwitchState.OFF,
+ isDefaultDisplayDesktopEligible = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ NO_EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED(
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = true,
+ tabletModeStatus = SwitchState.OFF,
+ isDefaultDisplayDesktopEligible = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ EXTERNAL_MIRROR_CLAMSHELL_PROJECTED(
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = false,
+ tabletModeStatus = SwitchState.OFF,
+ isDefaultDisplayDesktopEligible = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ NO_EXTERNAL_MIRROR_CLAMSHELL_PROJECTED(
+ hasExternalDisplay = false,
+ extendedDisplayEnabled = false,
+ tabletModeStatus = SwitchState.OFF,
+ isDefaultDisplayDesktopEligible = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ EXTERNAL_EXTENDED_UNKNOWN_PROJECTED(
+ hasExternalDisplay = true,
+ extendedDisplayEnabled = true,
+ tabletModeStatus = SwitchState.UNKNOWN,
+ isDefaultDisplayDesktopEligible = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ ),
+ NO_EXTERNAL_EXTENDED_UNKNOWN_PROJECTED(
hasExternalDisplay = false,
extendedDisplayEnabled = true,
tabletModeStatus = SwitchState.UNKNOWN,
+ isDefaultDisplayDesktopEligible = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_MIRROR_UNKNOWN(
+ EXTERNAL_MIRROR_UNKNOWN_PROJECTED(
hasExternalDisplay = true,
extendedDisplayEnabled = false,
tabletModeStatus = SwitchState.UNKNOWN,
+ isDefaultDisplayDesktopEligible = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- NO_EXTERNAL_MIRROR_UNKNOWN(
+ NO_EXTERNAL_MIRROR_UNKNOWN_PROJECTED(
hasExternalDisplay = false,
extendedDisplayEnabled = false,
tabletModeStatus = SwitchState.UNKNOWN,
+ isDefaultDisplayDesktopEligible = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt
new file mode 100644
index 000000000000..ef394d81cc57
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopPipTransitionObserverTest.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.desktopmode
+
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.os.Binder
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import android.view.WindowManager.TRANSIT_PIP
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/**
+ * Tests for [DesktopPipTransitionObserver].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DesktopPipTransitionObserverTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopPipTransitionObserverTest : ShellTestCase() {
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ private lateinit var observer: DesktopPipTransitionObserver
+
+ private val transition = Binder()
+ private var onSuccessInvokedCount = 0
+
+ @Before
+ fun setUp() {
+ observer = DesktopPipTransitionObserver()
+
+ onSuccessInvokedCount = 0
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onTransitionReady_taskInPinnedWindowingMode_onSuccessInvoked() {
+ val taskId = 1
+ val pipTransition = createPendingPipTransition(taskId)
+ val successfulChange = createChange(taskId, WINDOWING_MODE_PINNED)
+ observer.addPendingPipTransition(pipTransition)
+
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(
+ TRANSIT_PIP, /* flags= */
+ 0
+ ).apply { addChange(successfulChange) },
+ )
+
+ assertThat(onSuccessInvokedCount).isEqualTo(1)
+ }
+
+ private fun createPendingPipTransition(
+ taskId: Int
+ ): DesktopPipTransitionObserver.PendingPipTransition {
+ return DesktopPipTransitionObserver.PendingPipTransition(
+ token = transition,
+ taskId = taskId,
+ onSuccess = { onSuccessInvokedCount += 1 },
+ )
+ }
+
+ private fun createChange(taskId: Int, windowingMode: Int): TransitionInfo.Change {
+ return TransitionInfo.Change(mock(), mock()).apply {
+ taskInfo =
+ TestRunningTaskInfoBuilder()
+ .setTaskId(taskId)
+ .setWindowingMode(windowingMode)
+ .build()
+ }
+ }
+}
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 c455205f6411..a10aeca95bce 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
@@ -1237,36 +1237,6 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
}
@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.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
- repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
- repo.setTaskInPip(DEFAULT_DESKTOP_ID, taskId = 1, enterPip = true)
- repo.setTaskInPip(SECOND_DISPLAY, taskId = 2, enterPip = true)
-
- assertThat(repo.isTaskMinimizedPipInDisplay(DEFAULT_DESKTOP_ID, taskId = 1)).isTrue()
- assertThat(repo.isTaskMinimizedPipInDisplay(SECOND_DISPLAY, taskId = 2)).isTrue()
- }
-
- @Test
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun addTask_deskDoesNotExists_createsDesk() {
repo.addTask(displayId = 999, taskId = 6, isVisible = true)
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 240140499e69..7efcd4fc3c8f 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
@@ -265,6 +265,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Mock private lateinit var desksOrganizer: DesksOrganizer
@Mock private lateinit var userProfileContexts: UserProfileContexts
@Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
+ @Mock private lateinit var desktopPipTransitionObserver: DesktopPipTransitionObserver
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var mockDisplayContext: Context
@Mock private lateinit var dragToDisplayTransitionHandler: DragToDisplayTransitionHandler
@@ -393,6 +394,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
whenever(userProfileContexts[anyInt()]).thenReturn(context)
whenever(userProfileContexts.getOrCreate(anyInt())).thenReturn(context)
+ whenever(freeformTaskTransitionStarter.startPipTransition(any())).thenReturn(Binder())
controller = createController()
controller.setSplitScreenController(splitScreenController)
@@ -457,6 +459,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
overviewToDesktopTransitionObserver,
desksOrganizer,
desksTransitionsObserver,
+ Optional.of(desktopPipTransitionObserver),
userProfileContexts,
desktopModeCompatPolicy,
dragToDisplayTransitionHandler,
@@ -3502,6 +3505,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
fun onPipTaskMinimize_autoEnterEnabled_startPipTransition() {
val task = setUpPipTask(autoEnterEnabled = true)
val handler = mock(TransitionHandler::class.java)
@@ -3516,6 +3520,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
fun onPipTaskMinimize_autoEnterDisabled_startMinimizeTransition() {
val task = setUpPipTask(autoEnterEnabled = false)
whenever(
@@ -3535,6 +3540,90 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ )
+ fun onDesktopTaskEnteredPip_pipIsLastTask_removesWallpaper() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+
+ controller.onDesktopTaskEnteredPip(
+ taskId = task.taskId,
+ deskId = DEFAULT_DISPLAY,
+ displayId = task.displayId,
+ taskIsLastVisibleTaskBeforePip = true,
+ )
+
+ // Wallpaper is moved to the back
+ val wct = getLatestTransition()
+ wct.assertReorder(wallpaperToken, /* toTop= */ false)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun onDesktopTaskEnteredPip_pipIsLastTask_deactivatesDesk() {
+ val deskId = DEFAULT_DISPLAY
+ val task = setUpPipTask(autoEnterEnabled = true, deskId = deskId)
+ val transition = Binder()
+ whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)
+
+ controller.onDesktopTaskEnteredPip(
+ taskId = task.taskId,
+ deskId = deskId,
+ displayId = task.displayId,
+ taskIsLastVisibleTaskBeforePip = true,
+ )
+
+ verify(desksOrganizer).deactivateDesk(any(), eq(deskId))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onDesktopTaskEnteredPip_pipIsLastTask_launchesHome() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+
+ controller.onDesktopTaskEnteredPip(
+ taskId = task.taskId,
+ deskId = DEFAULT_DISPLAY,
+ displayId = task.displayId,
+ taskIsLastVisibleTaskBeforePip = true,
+ )
+
+ val wct = getLatestTransition()
+ wct.assertPendingIntent(launchHomeIntent(DEFAULT_DISPLAY))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+ fun onDesktopTaskEnteredPip_pipIsNotLastTask_doesntExitDesktopMode() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+ val deskId = DEFAULT_DISPLAY
+ setUpFreeformTask(deskId = deskId) // launch another freeform task
+ val transition = Binder()
+ whenever(transitions.startTransition(any(), any(), anyOrNull())).thenReturn(transition)
+
+ controller.onDesktopTaskEnteredPip(
+ taskId = task.taskId,
+ deskId = deskId,
+ displayId = task.displayId,
+ taskIsLastVisibleTaskBeforePip = false,
+ )
+
+ // No transition to exit Desktop mode is started
+ verifyWCTNotExecuted()
+ verify(desktopModeEnterExitTransitionListener, never())
+ .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ verify(desksOrganizer, never()).deactivateDesk(any(), eq(deskId))
+ verify(desksTransitionsObserver, never())
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+ }
+
+ @Test
fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() {
val task = setUpFreeformTask(active = true)
val transition = Binder()
@@ -5015,6 +5104,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun handleRequest_closeTransition_singleTaskNoToken_secondaryDisplay_launchesHome() {
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
taskRepository.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
@@ -7655,8 +7745,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
return task
}
- private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo =
- setUpFreeformTask().apply {
+ private fun setUpPipTask(
+ autoEnterEnabled: Boolean,
+ displayId: Int = DEFAULT_DISPLAY,
+ deskId: Int = DEFAULT_DISPLAY,
+ ): RunningTaskInfo =
+ setUpFreeformTask(displayId = displayId, deskId = deskId).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 a7dc706eb6c9..5ef1ace7873d 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,7 +22,6 @@ 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.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -30,7 +29,6 @@ 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
@@ -41,7 +39,6 @@ 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
@@ -51,10 +48,9 @@ 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 java.util.Optional
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -90,6 +86,7 @@ class DesktopTasksTransitionObserverTest {
private val userRepositories = mock<DesktopUserRepositories>()
private val taskRepository = mock<DesktopRepository>()
private val mixedHandler = mock<DesktopMixedTransitionHandler>()
+ private val pipTransitionObserver = mock<DesktopPipTransitionObserver>()
private val backAnimationController = mock<BackAnimationController>()
private val desktopWallpaperActivityTokenProvider =
mock<DesktopWallpaperActivityTokenProvider>()
@@ -114,6 +111,7 @@ class DesktopTasksTransitionObserverTest {
transitions,
shellTaskOrganizer,
mixedHandler,
+ Optional.of(pipTransitionObserver),
backAnimationController,
desktopWallpaperActivityTokenProvider,
shellInit,
@@ -336,67 +334,61 @@ class DesktopTasksTransitionObserverTest {
}
@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)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ fun nonTopTransparentTaskOpened_clearTopTransparentTaskIdFromRepository() {
+ val mockTransition = Mockito.mock(IBinder::class.java)
+ val topTransparentTask = createTaskInfo(1)
+ val nonTopTransparentTask = createTaskInfo(2)
+ whenever(taskRepository.getTopTransparentFullscreenTaskId(any()))
+ .thenReturn(topTransparentTask.taskId)
transitionObserver.onTransitionReady(
- transition = pipTransition,
- info = createOpenChangeTransition(task, type = TRANSIT_PIP),
+ transition = mockTransition,
+ info = createOpenChangeTransition(nonTopTransparentTask),
startTransaction = mock(),
finishTransaction = mock(),
)
- transitionObserver.onTransitionFinished(transition = pipTransition, aborted = true)
- verify(taskRepository).onPipAborted(task.displayId, task.taskId)
+ verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId)
}
@Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun exitPipTransition_taskRepositoryClearTaskInPip() {
- val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
- whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC,
+ )
+ fun nonTopTransparentTaskSentToFront_clearTopTransparentTaskIdFromRepository() {
+ val mockTransition = Mockito.mock(IBinder::class.java)
+ val topTransparentTask = createTaskInfo(1)
+ val nonTopTransparentTask = createTaskInfo(2)
+ whenever(taskRepository.getTopTransparentFullscreenTaskId(any()))
+ .thenReturn(topTransparentTask.taskId)
transitionObserver.onTransitionReady(
- transition = mock(),
- info = createOpenChangeTransition(task, type = TRANSIT_EXIT_PIP),
+ transition = mockTransition,
+ info = createToFrontTransition(nonTopTransparentTask),
startTransaction = mock(),
finishTransaction = mock(),
)
- verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
+ verify(taskRepository).clearTopTransparentFullscreenTaskId(topTransparentTask.displayId)
}
@Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
- fun removePipTransition_taskRepositoryClearTaskInPip() {
- val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
- whenever(taskRepository.isTaskMinimizedPipInDisplay(any(), any())).thenReturn(true)
+ fun transitCloseWallpaper_wallpaperActivityVisibilitySaved() {
+ val wallpaperTask = createWallpaperTaskInfo()
transitionObserver.onTransitionReady(
transition = mock(),
- info = createOpenChangeTransition(task, type = TRANSIT_REMOVE_PIP),
+ info = createCloseTransition(wallpaperTask),
startTransaction = mock(),
finishTransaction = mock(),
)
- verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
+ verify(desktopWallpaperActivityTokenProvider).removeToken(wallpaperTask.displayId)
}
private fun createBackNavigationTransition(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
index 7560945856ec..dc973d0fda77 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
@@ -90,7 +90,7 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() {
whenever(packageManager.getHomeActivities(ArrayList())).thenReturn(componentName)
desktopModeCompatPolicy = DesktopModeCompatPolicy(spyContext)
transitionHandler = createTransitionHandler()
- allowOverlayPermission(arrayOf(SYSTEM_ALERT_WINDOW))
+ allowOverlayPermissionForAllUsers(arrayOf(SYSTEM_ALERT_WINDOW))
}
private fun createTransitionHandler() =
@@ -200,10 +200,16 @@ class SystemModalsTransitionHandlerTest : ShellTestCase() {
.isTrue()
}
- fun allowOverlayPermission(permissions: Array<String>) {
+ fun allowOverlayPermissionForAllUsers(permissions: Array<String>) {
val packageInfo = mock<PackageInfo>()
packageInfo.requestedPermissions = permissions
- whenever(packageManager.getPackageInfo(anyString(), eq(PackageManager.GET_PERMISSIONS)))
+ whenever(
+ packageManager.getPackageInfoAsUser(
+ anyString(),
+ eq(PackageManager.GET_PERMISSIONS),
+ anyInt(),
+ )
+ )
.thenReturn(packageInfo)
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
index 5ac680048a7e..12785c03aa9f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
@@ -42,6 +42,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
@@ -87,7 +88,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun testIsTopActivityExemptWithPermission_onlyTransparentActivitiesInStack() {
- allowOverlayPermission(arrayOf(SYSTEM_ALERT_WINDOW))
+ allowOverlayPermissionForAllUsers(arrayOf(SYSTEM_ALERT_WINDOW))
assertTrue(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
.apply {
@@ -101,7 +102,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun testIsTopActivityExemptWithNoPermission_onlyTransparentActivitiesInStack() {
- allowOverlayPermission(arrayOf())
+ allowOverlayPermissionForAllUsers(arrayOf())
assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
.apply {
@@ -115,7 +116,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun testIsTopActivityExemptCachedPermissionCheckIsUsed() {
- allowOverlayPermission(arrayOf())
+ allowOverlayPermissionForAllUsers(arrayOf())
assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
.apply {
@@ -123,6 +124,7 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
isTopActivityNoDisplay = false
numActivities = 1
baseActivity = baseActivityTest
+ userId = 10
}))
assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
createFreeformTask(/* displayId */ 0)
@@ -131,10 +133,26 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
isTopActivityNoDisplay = false
numActivities = 1
baseActivity = baseActivityTest
+ userId = 10
}))
- verify(packageManager, times(1)).getPackageInfo(
+ assertFalse(desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = false
+ numActivities = 1
+ baseActivity = baseActivityTest
+ userId = 0
+ }))
+ verify(packageManager, times(1)).getPackageInfoAsUser(
+ eq("com.test.dummypackage"),
+ eq(PackageManager.GET_PERMISSIONS),
+ eq(10)
+ )
+ verify(packageManager, times(1)).getPackageInfoAsUser(
eq("com.test.dummypackage"),
- eq(PackageManager.GET_PERMISSIONS)
+ eq(PackageManager.GET_PERMISSIONS),
+ eq(0)
)
}
@@ -284,13 +302,14 @@ class DesktopModeCompatPolicyTest : ShellTestCase() {
}
}
- fun allowOverlayPermission(permissions: Array<String>) {
+ fun allowOverlayPermissionForAllUsers(permissions: Array<String>) {
val packageInfo = mock<PackageInfo>()
packageInfo.requestedPermissions = permissions
whenever(
- packageManager.getPackageInfo(
+ packageManager.getPackageInfoAsUser(
anyString(),
- eq(PackageManager.GET_PERMISSIONS)
+ eq(PackageManager.GET_PERMISSIONS),
+ anyInt(),
)
).thenReturn(packageInfo)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt
index 7341e098add5..360099777bde 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt
@@ -40,11 +40,13 @@ import com.android.wm.shell.windowdecor.DragResizeInputListener.TaskResizeInputE
import com.google.common.truth.Truth.assertThat
import java.util.function.Consumer
import java.util.function.Supplier
+import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argThat
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
@@ -63,6 +65,15 @@ class DragResizeInputListenerTest : ShellTestCase() {
private val testBgExecutor = TestShellExecutor()
private val mockWindowSession = mock<IWindowSession>()
private val mockInputEventReceiver = mock<TaskResizeInputEventReceiver>()
+ private val inputChannel = mock<InputChannel>()
+ private val sinkInputChannel = mock<InputChannel>()
+ private val decorationSurface = SurfaceControl.Builder().setName("decoration surface").build()
+ private val createdSurfaces = ArrayList<SurfaceControl>()
+
+ @After
+ fun tearDown() {
+ decorationSurface.release()
+ }
@Test
fun testGrantInputChannelOffMainThread() {
@@ -73,6 +84,35 @@ class DragResizeInputListenerTest : ShellTestCase() {
}
@Test
+ fun testGrantInputChannelAfterDecorSurfaceReleased() {
+ // Keep tracking the underlying surface that the decorationSurface points to.
+ val forVerification = SurfaceControl(decorationSurface, "forVerification")
+ try {
+ create()
+ decorationSurface.release()
+ testBgExecutor.flushAll()
+
+ verify(mockWindowSession)
+ .grantInputChannel(
+ anyInt(),
+ argThat<SurfaceControl> { isValid && isSameSurface(forVerification) },
+ any(),
+ anyOrNull(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ anyOrNull(),
+ any(),
+ any(),
+ any(),
+ )
+ } finally {
+ forVerification.release()
+ }
+ }
+
+ @Test
fun testInitializationCallback_waitsForBgSetup() {
val inputListener = create()
@@ -143,6 +183,40 @@ class DragResizeInputListenerTest : ShellTestCase() {
verify(mockWindowSession).remove(inputListener.mSinkClientToken)
}
+ @Test
+ fun testClose_afterBgSetup_disposesOfInputChannels() {
+ val inputListener = create()
+ testBgExecutor.flushAll()
+ inputListener.close()
+ testMainExecutor.flushAll()
+ verify(inputChannel).dispose()
+ verify(sinkInputChannel).dispose()
+ }
+
+ @Test
+ fun testClose_beforeBgSetup_releaseSurfaces() {
+ val inputListener = create()
+ inputListener.close()
+ testBgExecutor.flushAll()
+ testMainExecutor.flushAll()
+
+ assertThat(createdSurfaces).hasSize(1)
+ assertThat(createdSurfaces[0].isValid).isFalse()
+ }
+
+ @Test
+ fun testClose_afterBgSetup_releaseSurfaces() {
+ val inputListener = create()
+ testBgExecutor.flushAll()
+ inputListener.close()
+ testMainExecutor.flushAll()
+ testBgExecutor.flushAll()
+
+ assertThat(createdSurfaces).hasSize(2)
+ assertThat(createdSurfaces[0].isValid).isFalse()
+ assertThat(createdSurfaces[1].isValid).isFalse()
+ }
+
private fun verifyNoInputChannelGrantRequests() {
verify(mockWindowSession, never())
.grantInputChannel(
@@ -172,12 +246,26 @@ class DragResizeInputListenerTest : ShellTestCase() {
TestHandler(Looper.getMainLooper()),
mock<Choreographer>(),
Display.DEFAULT_DISPLAY,
- mock<SurfaceControl>(),
+ decorationSurface,
mock<DragPositioningCallback>(),
- { SurfaceControl.Builder() },
- { StubTransaction() },
+ {
+ object : SurfaceControl.Builder() {
+ override fun build(): SurfaceControl {
+ return super.build().also { createdSurfaces.add(it) }
+ }
+ }
+ },
+ {
+ object : StubTransaction() {
+ override fun remove(sc: SurfaceControl): SurfaceControl.Transaction {
+ return super.remove(sc).also { sc.release() }
+ }
+ }
+ },
mock<DisplayController>(),
mock<DesktopModeEventLogger>(),
+ inputChannel,
+ sinkInputChannel,
)
private class TestInitializationCallback : Runnable {