summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> 2024-08-21 20:21:53 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-08-21 20:21:53 +0000
commitddc55e99a16342365bd6b345e8ff26cfc58ad06e (patch)
tree527693c65c06e3a1285aec0ff77be2dc49cca3ec
parent2192b71429a487501277ebbaccbed598cbce085b (diff)
parentd5ced7da2a148facc1173c8313e3f8861b529ba5 (diff)
Merge "Implement a minimize button on a caption bar" into main
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_header_ic_minimize.xml26
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml12
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt31
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt72
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt39
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 {