diff options
| author | 2024-08-21 20:21:53 +0000 | |
|---|---|---|
| committer | 2024-08-21 20:21:53 +0000 | |
| commit | ddc55e99a16342365bd6b345e8ff26cfc58ad06e (patch) | |
| tree | 527693c65c06e3a1285aec0ff77be2dc49cca3ec | |
| parent | 2192b71429a487501277ebbaccbed598cbce085b (diff) | |
| parent | d5ced7da2a148facc1173c8313e3f8861b529ba5 (diff) | |
Merge "Implement a minimize button on a caption bar" into main
13 files changed, 244 insertions, 17 deletions
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_minimize.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_minimize.xml new file mode 100644 index 000000000000..b35dc022e210 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_minimize.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#FF000000" + android:pathData="M6,21V19H18V21Z"/> +</vector> diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml index 7b31c1420a7c..7dcb3c237c51 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml @@ -76,6 +76,18 @@ android:layout_height="40dp" android:layout_weight="1"/> + <ImageButton + android:id="@+id/minimize_window" + android:layout_width="44dp" + android:layout_height="40dp" + android:paddingHorizontal="10dp" + android:paddingVertical="8dp" + android:layout_marginEnd="8dp" + android:contentDescription="@string/minimize_button_text" + android:src="@drawable/desktop_mode_header_ic_minimize" + android:scaleType="centerCrop" + android:gravity="end"/> + <com.android.wm.shell.windowdecor.MaximizeButtonView android:id="@+id/maximize_button_view" android:layout_width="44dp" diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 08a746fdf867..2d98a2b675a3 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -460,6 +460,11 @@ start of this area. --> <dimen name="desktop_mode_customizable_caption_margin_end">152dp</dimen> + <!-- The width of the right-aligned region that is taken up by caption elements and extra + margins when the caption has the minimize button. This will be merged with the above value + once the minimize button becomes default. --> + <dimen name="desktop_mode_customizable_caption_with_minimize_button_margin_end">204dp</dimen> + <!-- The default minimum allowed window width when resizing a window in desktop mode. --> <dimen name="desktop_mode_minimum_window_width">386dp</dimen> @@ -579,6 +584,13 @@ <!-- The vertical inset to apply to the app chip's ripple drawable --> <dimen name="desktop_mode_header_app_chip_ripple_inset_vertical">4dp</dimen> + <!-- The corner radius of the minimize button's ripple drawable --> + <dimen name="desktop_mode_header_minimize_ripple_radius">18dp</dimen> + <!-- The vertical inset to apply to the minimize button's ripple drawable --> + <dimen name="desktop_mode_header_minimize_ripple_inset_vertical">4dp</dimen> + <!-- The horizontal inset to apply to the minimize button's ripple drawable --> + <dimen name="desktop_mode_header_minimize_ripple_inset_horizontal">6dp</dimen> + <!-- The corner radius of the maximize button's ripple drawable --> <dimen name="desktop_mode_header_maximize_ripple_radius">18dp</dimen> <!-- The vertical inset to apply to the maximize button's ripple drawable --> 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 d94732681f72..4db8a82eb5af 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 @@ -236,7 +236,8 @@ public abstract class WMShellModule { RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, InteractionJankMonitor interactionJankMonitor, AppToWebGenericLinksParser genericLinksParser, - MultiInstanceHelper multiInstanceHelper) { + MultiInstanceHelper multiInstanceHelper, + Optional<DesktopTasksLimiter> desktopTasksLimiter) { if (DesktopModeStatus.canEnterDesktopMode(context)) { return new DesktopModeWindowDecorViewModel( context, @@ -257,7 +258,8 @@ public abstract class WMShellModule { rootTaskDisplayAreaOrganizer, interactionJankMonitor, genericLinksParser, - multiInstanceHelper); + multiInstanceHelper, + desktopTasksLimiter); } return new CaptionWindowDecorViewModel( context, 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 33794d242c03..c97066a6e892 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 @@ -431,6 +431,20 @@ class DesktopTasksController( taskRepository.addClosingTask(displayId, taskId) } + /** + * Perform clean up of the desktop wallpaper activity if the minimized window task is the last + * active task. + * + * @param wct transaction to modify if the last active task is minimized + * @param taskId task id of the window that's being minimized + */ + fun onDesktopWindowMinimize(wct: WindowContainerTransaction, taskId: Int) { + if (taskRepository.isOnlyVisibleNonClosingTask(taskId)) { + removeWallpaperActivity(wct) + } + // Do not call taskRepository.minimizeTask because it will be called by DekstopTasksLimiter. + } + /** Move a task with given `taskId` to fullscreen */ fun moveToFullscreen(taskId: Int, transitionSource: DesktopModeTransitionSource) { shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index 4284d06a293f..1ffa54103d62 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -18,6 +18,7 @@ package com.android.wm.shell.freeform; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE; import android.animation.Animator; @@ -116,9 +117,11 @@ public class FreeformTaskTransitionHandler } @Override - public void startMinimizedModeTransition(WindowContainerTransaction wct) { + public IBinder startMinimizedModeTransition(WindowContainerTransaction wct) { final int type = WindowManager.TRANSIT_TO_BACK; - mPendingTransitionTokens.add(mTransitions.startTransition(type, wct, this)); + final IBinder token = mTransitions.startTransition(type, wct, this); + mPendingTransitionTokens.add(token); + return token; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java index 8da4c6ab4b36..ea68a694c3b9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java @@ -16,6 +16,7 @@ package com.android.wm.shell.freeform; +import android.os.IBinder; import android.window.WindowContainerTransaction; /** @@ -38,8 +39,9 @@ public interface FreeformTaskTransitionStarter { * * @param wct the {@link WindowContainerTransaction} that changes the windowing mode * + * @return the started transition */ - void startMinimizedModeTransition(WindowContainerTransaction wct); + IBinder startMinimizedModeTransition(WindowContainerTransaction wct); /** * Starts close window transition diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 457b5112076e..b1cb834db9bf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -56,6 +56,7 @@ import android.graphics.Region; import android.hardware.input.InputManager; import android.net.Uri; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; @@ -100,6 +101,7 @@ import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition; +import com.android.wm.shell.desktopmode.DesktopTasksLimiter; import com.android.wm.shell.desktopmode.DesktopWallpaperActivity; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; @@ -150,6 +152,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final InputManager mInputManager; private final InteractionJankMonitor mInteractionJankMonitor; private final MultiInstanceHelper mMultiInstanceHelper; + private final Optional<DesktopTasksLimiter> mDesktopTasksLimiter; private boolean mTransitionDragActive; private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>(); @@ -211,7 +214,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, InteractionJankMonitor interactionJankMonitor, AppToWebGenericLinksParser genericLinksParser, - MultiInstanceHelper multiInstanceHelper + MultiInstanceHelper multiInstanceHelper, + Optional<DesktopTasksLimiter> desktopTasksLimiter ) { this( context, @@ -236,7 +240,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { SurfaceControl.Transaction::new, rootTaskDisplayAreaOrganizer, new SparseArray<>(), - interactionJankMonitor); + interactionJankMonitor, + desktopTasksLimiter); } @VisibleForTesting @@ -263,7 +268,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { Supplier<SurfaceControl.Transaction> transactionFactory, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, + Optional<DesktopTasksLimiter> desktopTasksLimiter) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; @@ -290,6 +296,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mSysUIPackageName = mContext.getResources().getString( com.android.internal.R.string.config_systemUi); mInteractionJankMonitor = interactionJankMonitor; + mDesktopTasksLimiter = desktopTasksLimiter; mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> { DesktopModeWindowDecoration decoration; RunningTaskInfo taskInfo; @@ -615,6 +622,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { // {@link DesktopModeWindowDecoration#setOnMaximizeOrRestoreClickListener}, which // should shared with the maximize menu's maximize/restore actions. onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button"); + } else if (id == R.id.minimize_window) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + mDesktopTasksController.onDesktopWindowMinimize(wct, mTaskId); + final IBinder transition = mTaskOperations.minimizeTask(mTaskToken, wct); + mDesktopTasksLimiter.ifPresent(limiter -> + limiter.addPendingMinimizeChange(transition, mDisplayId, mTaskId)); } } @@ -628,7 +641,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } if (id != R.id.caption_handle && id != R.id.desktop_mode_caption && id != R.id.open_menu_button && id != R.id.close_window - && id != R.id.maximize_window) { + && id != R.id.maximize_window && id != R.id.minimize_window) { return false; } @@ -768,7 +781,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return true; } final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window - || id == R.id.open_menu_button); + || id == R.id.open_menu_button || id == R.id.minimize_window); switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { mDragPointerId = e.getPointerId(0); 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 75a6cd7b720e..8e87d0ff33c6 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 @@ -643,6 +643,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final RelayoutParams.OccludingCaptionElement controlsElement = new RelayoutParams.OccludingCaptionElement(); controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end; + if (Flags.enableMinimizeButton()) { + controlsElement.mWidthResId = + R.dimen.desktop_mode_customizable_caption_with_minimize_button_margin_end; + } controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END; relayoutParams.mOccludingCaptionElements.add(controlsElement); } else if (isAppHandle && !Flags.enableAdditionalWindowsAboveStatusBar()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java index ad238c35dd83..61b93932013c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java @@ -23,6 +23,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.hardware.input.InputManager; +import android.os.IBinder; import android.os.SystemClock; import android.util.Log; import android.view.InputDevice; @@ -84,13 +85,17 @@ class TaskOperations { } } - void minimizeTask(WindowContainerToken taskToken) { - WindowContainerTransaction wct = new WindowContainerTransaction(); + IBinder minimizeTask(WindowContainerToken taskToken) { + return minimizeTask(taskToken, new WindowContainerTransaction()); + } + + IBinder minimizeTask(WindowContainerToken taskToken, WindowContainerTransaction wct) { wct.reorder(taskToken, false); if (Transitions.ENABLE_SHELL_TRANSITIONS) { - mTransitionStarter.startMinimizedModeTransition(wct); + return mTransitionStarter.startMinimizedModeTransition(wct); } else { mSyncQueue.queue(wct); + return null; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt index d0eb6da36702..033d69583725 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt @@ -35,6 +35,7 @@ import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.ui.graphics.toArgb import androidx.core.content.withStyledAttributes +import androidx.core.view.isGone import androidx.core.view.isVisible import com.android.internal.R.attr.materialColorOnSecondaryContainer import com.android.internal.R.attr.materialColorOnSurface @@ -42,6 +43,7 @@ import com.android.internal.R.attr.materialColorSecondaryContainer import com.android.internal.R.attr.materialColorSurfaceContainerHigh import com.android.internal.R.attr.materialColorSurfaceContainerLow import com.android.internal.R.attr.materialColorSurfaceDim +import com.android.window.flags.Flags.enableMinimizeButton import com.android.wm.shell.R import com.android.wm.shell.shared.desktopmode.DesktopModeFlags import com.android.wm.shell.windowdecor.MaximizeButtonView @@ -82,9 +84,9 @@ internal class AppHeaderViewHolder( .getDimensionPixelSize(R.dimen.desktop_mode_header_buttons_ripple_radius) /** - * The app chip, maximize and close button's height extends to the top & bottom edges of the - * header, and their width may be larger than their height. This is by design to increase the - * clickable and hover-able bounds of the view as much as possible. However, to prevent the + * The app chip, minimize, maximize and close button's height extends to the top & bottom edges + * of the header, and their width may be larger than their height. This is by design to increase + * the clickable and hover-able bounds of the view as much as possible. However, to prevent the * ripple drawable from being as large as the views (and asymmetrical), insets are applied to * the background ripple drawable itself to give the appearance of a smaller button * (with padding between itself and the header edges / sibling buttons) but without affecting @@ -94,6 +96,12 @@ internal class AppHeaderViewHolder( vertical = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_header_app_chip_ripple_inset_vertical) ) + private val minimizeDrawableInsets = DrawableInsets( + vertical = context.resources + .getDimensionPixelSize(R.dimen.desktop_mode_header_minimize_ripple_inset_vertical), + horizontal = context.resources + .getDimensionPixelSize(R.dimen.desktop_mode_header_minimize_ripple_inset_horizontal) + ) private val maximizeDrawableInsets = DrawableInsets( vertical = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_header_maximize_ripple_inset_vertical), @@ -115,6 +123,7 @@ internal class AppHeaderViewHolder( private val maximizeButtonView: MaximizeButtonView = rootView.requireViewById(R.id.maximize_button_view) private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window) + private val minimizeWindowButton: ImageButton = rootView.requireViewById(R.id.minimize_window) private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name) private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon) val appNameTextWidth: Int @@ -131,6 +140,8 @@ internal class AppHeaderViewHolder( maximizeWindowButton.setOnGenericMotionListener(onCaptionGenericMotionListener) maximizeWindowButton.onLongClickListener = onLongClickListener closeWindowButton.setOnTouchListener(onCaptionTouchListener) + minimizeWindowButton.setOnClickListener(onCaptionButtonClickListener) + minimizeWindowButton.setOnTouchListener(onCaptionTouchListener) appNameTextView.text = appName appIconImageView.setImageBitmap(appIconBitmap) maximizeButtonView.onHoverAnimationFinishedListener = @@ -157,11 +168,13 @@ internal class AppHeaderViewHolder( val alpha = Color.alpha(color) closeWindowButton.imageTintList = ColorStateList.valueOf(color) maximizeWindowButton.imageTintList = ColorStateList.valueOf(color) + minimizeWindowButton.imageTintList = ColorStateList.valueOf(color) expandMenuButton.imageTintList = ColorStateList.valueOf(color) appNameTextView.isVisible = !taskInfo.isTransparentCaptionBarAppearance appNameTextView.setTextColor(color) appIconImageView.imageAlpha = alpha maximizeWindowButton.imageAlpha = alpha + minimizeWindowButton.imageAlpha = alpha closeWindowButton.imageAlpha = alpha expandMenuButton.imageAlpha = alpha context.withStyledAttributes( @@ -176,8 +189,10 @@ internal class AppHeaderViewHolder( openMenuButton.background = getDrawable(0) maximizeWindowButton.background = getDrawable(1) closeWindowButton.background = getDrawable(1) + minimizeWindowButton.background = getDrawable(1) } maximizeButtonView.setAnimationTints(isDarkMode()) + minimizeWindowButton.isGone = !enableMinimizeButton() } private fun bindDataWithThemedHeaders(taskInfo: RunningTaskInfo) { @@ -212,6 +227,16 @@ internal class AppHeaderViewHolder( } appIconImageView.imageAlpha = foregroundAlpha } + // Minimize button. + minimizeWindowButton.apply { + imageTintList = colorStateList + background = createRippleDrawable( + color = foregroundColor, + cornerRadius = headerButtonsRippleRadius, + drawableInsets = minimizeDrawableInsets + ) + } + minimizeWindowButton.isGone = !enableMinimizeButton() // Maximize button. maximizeButtonView.setAnimationTints( darkMode = header.appTheme == Theme.DARK, 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 a841e168af18..2e0af273f2b6 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 @@ -1429,6 +1429,78 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun onDesktopWindowMinimize_noActiveTask_doesntUpdateTransaction() { + val wct = WindowContainerTransaction() + controller.onDesktopWindowMinimize(wct, taskId = 1) + // Nothing happens. + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test + fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntUpdateTransaction() { + val task = setUpFreeformTask() + val wct = WindowContainerTransaction() + controller.onDesktopWindowMinimize(wct, taskId = task.taskId) + // Nothing happens. + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test + fun onDesktopWindowMinimize_singleActiveTask_hasWallpaperActivityToken_removesWallpaper() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + + val wct = WindowContainerTransaction() + // The only active task is being minimized. + controller.onDesktopWindowMinimize(wct, taskId = task.taskId) + // Adds remove wallpaper operation + wct.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test + fun onDesktopWindowMinimize_singleActiveTask_alreadyMinimized_doesntUpdateTransaction() { + val task = setUpFreeformTask() + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId) + + val wct = WindowContainerTransaction() + // The only active task is already minimized. + controller.onDesktopWindowMinimize(wct, taskId = task.taskId) + // Doesn't modify transaction + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test + fun onDesktopWindowMinimize_multipleActiveTasks_doesntUpdateTransaction() { + val task1 = setUpFreeformTask() + setUpFreeformTask() + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + + val wct = WindowContainerTransaction() + controller.onDesktopWindowMinimize(wct, taskId = task1.taskId) + // Doesn't modify transaction + assertThat(wct.hierarchyOps).isEmpty() + } + + @Test + fun onDesktopWindowMinimize_multipleActiveTasks_minimizesTheOnlyVisibleTask_removesWallpaper() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val wallpaperToken = MockToken().token() + taskRepository.wallpaperActivityToken = wallpaperToken + taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) + + val wct = WindowContainerTransaction() + // task1 is the only visible task as task2 is minimized. + controller.onDesktopWindowMinimize(wct, taskId = task1.taskId) + // Adds remove wallpaper operation + wct.assertRemoveAt(index = 0, wallpaperToken) + } + + @Test fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() { assumeTrue(ENABLE_SHELL_TRANSITIONS) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 4d6b3b907a65..543ec84e79f6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -82,6 +82,7 @@ import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition +import com.android.wm.shell.desktopmode.DesktopTasksLimiter import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.splitscreen.SplitScreenController @@ -112,6 +113,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argThat import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doNothing @@ -163,6 +165,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockToast: Toast private val bgExecutor = TestShellExecutor() @Mock private lateinit var mockMultiInstanceHelper: MultiInstanceHelper + @Mock private lateinit var mockTasksLimiter: DesktopTasksLimiter private lateinit var spyContext: TestableContext private val transactionFactory = Supplier<SurfaceControl.Transaction> { @@ -215,7 +218,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { transactionFactory, mockRootTaskDisplayAreaOrganizer, windowDecorByTaskIdSpy, - mockInteractionJankMonitor + mockInteractionJankMonitor, + Optional.of(mockTasksLimiter) ) desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController) whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout) @@ -388,6 +392,39 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } @Test + @EnableFlags(Flags.FLAG_ENABLE_MINIMIZE_BUTTON) + fun testMinimizeButtonInFreefrom_minimizeWindow() { + val onClickListenerCaptor = forClass(View.OnClickListener::class.java) + as ArgumentCaptor<View.OnClickListener> + val decor = createOpenTaskDecoration( + windowingMode = WINDOWING_MODE_FREEFORM, + onCaptionButtonClickListener = onClickListenerCaptor + ) + + val view = mock(View::class.java) + whenever(view.id).thenReturn(R.id.minimize_window) + + val freeformTaskTransitionStarter = mock(FreeformTaskTransitionStarter::class.java) + desktopModeWindowDecorViewModel + .setFreeformTaskTransitionStarter(freeformTaskTransitionStarter) + + onClickListenerCaptor.value.onClick(view) + + val transactionCaptor = argumentCaptor<WindowContainerTransaction>() + verify(freeformTaskTransitionStarter) + .startMinimizedModeTransition(transactionCaptor.capture()) + val wct = transactionCaptor.firstValue + + verify(mockTasksLimiter).addPendingMinimizeChange( + anyOrNull(), eq(DEFAULT_DISPLAY), eq(decor.mTaskInfo.taskId)) + + assertEquals(1, wct.getHierarchyOps().size) + assertEquals(HierarchyOp.HIERARCHY_OP_TYPE_REORDER, wct.getHierarchyOps().get(0).getType()) + assertFalse(wct.getHierarchyOps().get(0).getToTop()) + assertEquals(decor.mTaskInfo.token.asBinder(), wct.getHierarchyOps().get(0).getContainer()) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() { val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true).apply { |