diff options
Diffstat (limited to 'libs')
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 {  |