From 853a8e3ed9492b8fb987956ad160c387ed840dd2 Mon Sep 17 00:00:00 2001 From: Maryam Dehaini <mdehaini@google.com> Date: Fri, 12 Jan 2024 13:43:31 -0800 Subject: [PATCH] Add hover support to maximize menu Open maximize menu when user hovers over maximize window button in caption. Bug: 312748319 Test: Manual testing Change-Id: Ie5bc94c02c5555ea99e6e42f9f6a00c65e348172 --- ...ode_caption_button_color_selector_dark.xml | 21 ++++ ...de_caption_button_color_selector_light.xml | 21 ++++ .../Shell/res/drawable/circular_progress.xml | 33 ++++++ .../Shell/res/drawable/rounded_button.xml | 19 ++++ ...desktop_mode_app_controls_window_decor.xml | 16 +-- ...esktop_mode_window_decor_maximize_menu.xml | 1 + .../Shell/res/layout/maximize_menu_button.xml | 36 ++++++ .../WindowManager/Shell/res/values/colors.xml | 5 + .../DesktopModeWindowDecorViewModel.java | 53 +++++++-- .../DesktopModeWindowDecoration.java | 36 +++++- .../shell/windowdecor/MaximizeButtonView.kt | 106 ++++++++++++++++++ .../wm/shell/windowdecor/MaximizeMenu.kt | 36 +++++- ...deAppControlsWindowDecorationViewHolder.kt | 28 ++++- 13 files changed, 381 insertions(+), 30 deletions(-) create mode 100644 libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_dark.xml create mode 100644 libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_light.xml create mode 100644 libs/WindowManager/Shell/res/drawable/circular_progress.xml create mode 100644 libs/WindowManager/Shell/res/drawable/rounded_button.xml create mode 100644 libs/WindowManager/Shell/res/layout/maximize_menu_button.xml create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt diff --git a/libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_dark.xml b/libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_dark.xml new file mode 100644 index 000000000000..52a59671baa1 --- /dev/null +++ b/libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_dark.xml @@ -0,0 +1,21 @@ +<?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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_hovered="true" + android:color="@color/desktop_mode_caption_button_on_hover_dark"/> + <item android:color="@color/desktop_mode_caption_button"/> +</selector> \ No newline at end of file diff --git a/libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_light.xml b/libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_light.xml new file mode 100644 index 000000000000..6d8a51cd6f8f --- /dev/null +++ b/libs/WindowManager/Shell/res/color/desktop_mode_caption_button_color_selector_light.xml @@ -0,0 +1,21 @@ +<?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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_hovered="true" + android:color="@color/desktop_mode_caption_button_on_hover_light"/> + <item android:color="@color/desktop_mode_caption_button"/> +</selector> \ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/circular_progress.xml b/libs/WindowManager/Shell/res/drawable/circular_progress.xml new file mode 100644 index 000000000000..948264579e1d --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/circular_progress.xml @@ -0,0 +1,33 @@ +<?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. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@android:id/progress"> + <rotate + android:pivotX="50%" + android:pivotY="50%" + android:fromDegrees="275" + android:toDegrees="275"> + <shape + android:shape="ring" + android:thickness="3dp" + android:innerRadius="17dp" + android:useLevel="true"> + </shape> + </rotate> + </item> +</layer-list> \ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/rounded_button.xml b/libs/WindowManager/Shell/res/drawable/rounded_button.xml new file mode 100644 index 000000000000..17a0bab56a74 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/rounded_button.xml @@ -0,0 +1,19 @@ +<?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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <corners android:radius="20dp" /> +</shape> \ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml index e4f793c2665b..d1b1af3e77ab 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml @@ -74,17 +74,11 @@ android:layout_height="40dp" android:layout_weight="1"/> - <ImageButton - android:id="@+id/maximize_window" - android:layout_width="40dp" - android:layout_height="40dp" - android:padding="9dp" - android:layout_marginEnd="8dp" - android:contentDescription="@string/maximize_button_text" - android:src="@drawable/decor_desktop_mode_maximize_button_dark" - android:scaleType="fitCenter" - android:gravity="end" - android:background="@null"/> + <com.android.wm.shell.windowdecor.MaximizeButtonView + android:id="@+id/maximize_button_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end"/> <ImageButton android:id="@+id/close_window" diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml index 0db72f7be8e6..dbfd6e5d8d94 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml @@ -15,6 +15,7 @@ ~ limitations under the License. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/maximize_menu" style="?android:attr/buttonBarStyle" android:layout_width="@dimen/desktop_mode_maximize_menu_width" android:layout_height="@dimen/desktop_mode_maximize_menu_height" diff --git a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml new file mode 100644 index 000000000000..bb6efcec1a70 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml @@ -0,0 +1,36 @@ +<?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. + --> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + <ProgressBar + android:id="@+id/progress_bar" + style="?android:attr/progressBarStyleHorizontal" + android:progressDrawable="@drawable/circular_progress" + android:layout_width="40dp" + android:layout_height="40dp" + android:indeterminate="false" + android:visibility="invisible"/> + + <ImageButton + android:id="@+id/maximize_window" + android:layout_width="40dp" + android:layout_height="40dp" + android:padding="9dp" + android:contentDescription="@string/maximize_button_text" + android:src="@drawable/decor_desktop_mode_maximize_button_dark" + android:scaleType="fitCenter" + android:background="@drawable/rounded_button"/> +</merge> \ No newline at end of file diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml index fae71efe3b39..758dbfd5f3c5 100644 --- a/libs/WindowManager/Shell/res/values/colors.xml +++ b/libs/WindowManager/Shell/res/values/colors.xml @@ -66,4 +66,9 @@ <color name="desktop_mode_maximize_menu_button_outline">#797869</color> <color name="desktop_mode_maximize_menu_button_outline_on_hover">#606219</color> <color name="desktop_mode_maximize_menu_button_on_hover">#E7E790</color> + <color name="desktop_mode_maximize_menu_progress_light">#33000000</color> + <color name="desktop_mode_maximize_menu_progress_dark">#33FFFFFF</color> + <color name="desktop_mode_caption_button_on_hover_light">#11000000</color> + <color name="desktop_mode_caption_button_on_hover_dark">#11FFFFFF</color> + <color name="desktop_mode_caption_button">#00000000</color> </resources> 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 57e964f15d50..5b8ffb30dc4a 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 @@ -22,6 +22,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; +import static android.view.MotionEvent.ACTION_HOVER_ENTER; +import static android.view.MotionEvent.ACTION_HOVER_EXIT; import static android.view.WindowInsets.Type.statusBars; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -311,8 +313,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener, - DragDetector.MotionEventHandler { - + View.OnGenericMotionListener , DragDetector.MotionEventHandler { + private static final int CLOSE_MAXIMIZE_MENU_DELAY_MS = 150; private final int mTaskId; private final WindowContainerToken mTaskToken; private final DragPositioningCallback mDragPositioningCallback; @@ -323,6 +325,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private boolean mHasLongClicked; private boolean mShouldClick; private int mDragPointerId = -1; + private final Runnable mCloseMaximizeWindowRunnable; private DesktopModeTouchEventListener( RunningTaskInfo taskInfo, @@ -332,6 +335,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragPositioningCallback = dragPositioningCallback; mDragDetector = new DragDetector(this); mGestureDetector = new GestureDetector(mContext, this); + mCloseMaximizeWindowRunnable = () -> { + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); + if (decoration == null) return; + decoration.closeMaximizeMenu(); + }; } @Override @@ -387,13 +395,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId)); } } else if (id == R.id.maximize_window) { - if (decoration.isMaximizeMenuActive()) { - decoration.closeMaximizeMenu(); - return; - } final RunningTaskInfo taskInfo = decoration.mTaskInfo; - mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo)); decoration.closeHandleMenu(); + decoration.closeMaximizeMenu(); + mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo)); } else if (id == R.id.maximize_menu_maximize_button) { final RunningTaskInfo taskInfo = decoration.mTaskInfo; mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo)); @@ -460,6 +465,36 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return false; } + @Override + public boolean onGenericMotion(View v, MotionEvent ev) { + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); + final int id = v.getId(); + if (ev.getAction() == ACTION_HOVER_ENTER) { + if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) { + decoration.onMaximizeWindowHoverEnter(); + } else if (id == R.id.maximize_window + || MaximizeMenu.Companion.isMaximizeMenuView(id)) { + // Re-hovering over any of the maximize menu views should keep the menu open by + // cancelling any attempts to close the menu. + mMainHandler.removeCallbacks(mCloseMaximizeWindowRunnable); + } + return true; + } else if (ev.getAction() == ACTION_HOVER_EXIT) { + if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) { + decoration.onMaximizeWindowHoverExit(); + } else if (id == R.id.maximize_window + || MaximizeMenu.Companion.isMaximizeMenuView(id)) { + // Close menu if not hovering over maximize menu or maximize button after a + // delay to give user a chance to re-enter view or to move from one maximize + // menu view to another. + mMainHandler.postDelayed(mCloseMaximizeWindowRunnable, + CLOSE_MAXIMIZE_MENU_DELAY_MS); + } + return true; + } + return false; + } + private void moveTaskToFront(RunningTaskInfo taskInfo) { if (!taskInfo.isFocused) { mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo)); @@ -990,7 +1025,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback); windowDecoration.setCaptionListeners( - touchEventListener, touchEventListener, touchEventListener); + touchEventListener, touchEventListener, touchEventListener, touchEventListener); windowDecoration.setExclusionRegionListener(mExclusionRegionListener); windowDecoration.setDragPositioningCallback(dragPositioningCallback); windowDecoration.setDragDetector(touchEventListener.mDragDetector); @@ -1036,6 +1071,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return; } decoration.showResizeVeil(t, bounds); + decoration.setAnimatingTaskResize(true); } @Override @@ -1050,6 +1086,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); if (decoration == null) return; decoration.hideResizeVeil(); + decoration.setAnimatingTaskResize(false); } } 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 185365b2a501..74f460bf1226 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 @@ -60,6 +60,8 @@ import com.android.wm.shell.windowdecor.viewholder.DesktopModeAppControlsWindowD import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder; import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder; +import kotlin.Unit; + import java.util.function.Supplier; /** @@ -79,6 +81,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private View.OnClickListener mOnCaptionButtonClickListener; private View.OnTouchListener mOnCaptionTouchListener; private View.OnLongClickListener mOnCaptionLongClickListener; + private View.OnGenericMotionListener mOnCaptionGenericMotionListener; private DragPositioningCallback mDragPositioningCallback; private DragResizeInputListener mDragResizeListener; private DragDetector mDragDetector; @@ -152,10 +155,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin void setCaptionListeners( View.OnClickListener onCaptionButtonClickListener, View.OnTouchListener onCaptionTouchListener, - View.OnLongClickListener onLongClickListener) { + View.OnLongClickListener onLongClickListener, + View.OnGenericMotionListener onGenericMotionListener) { mOnCaptionButtonClickListener = onCaptionButtonClickListener; mOnCaptionTouchListener = onCaptionTouchListener; mOnCaptionLongClickListener = onLongClickListener; + mOnCaptionGenericMotionListener = onGenericMotionListener; } void setExclusionRegionListener(ExclusionRegionListener exclusionRegionListener) { @@ -225,9 +230,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mOnCaptionTouchListener, mOnCaptionButtonClickListener, mOnCaptionLongClickListener, + mOnCaptionGenericMotionListener, mAppName, - mAppIconBitmap - ); + mAppIconBitmap, + () -> { + if (!isMaximizeMenuActive()) { + createMaximizeMenu(); + } + return Unit.INSTANCE; + }); } else { throw new IllegalArgumentException("Unexpected layout resource id"); } @@ -548,7 +559,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ void createMaximizeMenu() { mMaximizeMenu = new MaximizeMenu(mSyncQueue, mRootTaskDisplayAreaOrganizer, - mDisplayController, mTaskInfo, mOnCaptionButtonClickListener, mContext, + mDisplayController, mTaskInfo, mOnCaptionButtonClickListener, + mOnCaptionGenericMotionListener, mOnCaptionTouchListener, mContext, calculateMaximizeMenuPosition(), mSurfaceControlTransactionSupplier); mMaximizeMenu.show(); } @@ -776,6 +788,22 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return R.id.desktop_mode_caption; } + void setAnimatingTaskResize(boolean animatingTaskResize) { + if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_focused_window_decor) return; + ((DesktopModeAppControlsWindowDecorationViewHolder) mWindowDecorViewHolder) + .setAnimatingTaskResize(animatingTaskResize); + } + + void onMaximizeWindowHoverExit() { + ((DesktopModeAppControlsWindowDecorationViewHolder) mWindowDecorViewHolder) + .onMaximizeWindowHoverExit(); + } + + void onMaximizeWindowHoverEnter() { + ((DesktopModeAppControlsWindowDecorationViewHolder) mWindowDecorViewHolder) + .onMaximizeWindowHoverEnter(); + } + @Override public String toString() { return "{" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt new file mode 100644 index 000000000000..b2f8cfdbfb7a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt @@ -0,0 +1,106 @@ +/* + * 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. + */ +package com.android.wm.shell.windowdecor + +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.animation.ValueAnimator +import android.content.Context +import android.content.res.ColorStateList +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.ProgressBar +import androidx.core.animation.doOnEnd +import androidx.core.animation.doOnStart +import androidx.core.content.ContextCompat +import com.android.wm.shell.R + +private const val OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS = 350 +private const val MAX_DRAWABLE_ALPHA = 255 + +class MaximizeButtonView( + context: Context, + attrs: AttributeSet +) : FrameLayout(context, attrs) { + lateinit var onHoverAnimationFinishedListener: () -> Unit + private val hoverProgressAnimatorSet = AnimatorSet() + var hoverDisabled = false + + private val progressBar: ProgressBar + private val maximizeWindow: ImageButton + + init { + LayoutInflater.from(context).inflate(R.layout.maximize_menu_button, this, true) + + progressBar = requireViewById(R.id.progress_bar) + maximizeWindow = requireViewById(R.id.maximize_window) + } + + fun startHoverAnimation() { + if (hoverDisabled) return + if (hoverProgressAnimatorSet.isRunning) { + cancelHoverAnimation() + } + + maximizeWindow.background.alpha = 0 + + hoverProgressAnimatorSet.playSequentially( + ValueAnimator.ofInt(0, MAX_DRAWABLE_ALPHA) + .setDuration(50) + .apply { + addUpdateListener { + maximizeWindow.background.alpha = animatedValue as Int + } + }, + ObjectAnimator.ofInt(progressBar, "progress", 100) + .setDuration(OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS.toLong()) + .apply { + doOnStart { + progressBar.setProgress(0, false) + progressBar.visibility = View.VISIBLE + } + doOnEnd { + progressBar.visibility = View.INVISIBLE + onHoverAnimationFinishedListener() + } + } + ) + hoverProgressAnimatorSet.start() + } + + fun cancelHoverAnimation() { + hoverProgressAnimatorSet.removeAllListeners() + hoverProgressAnimatorSet.cancel() + progressBar.visibility = View.INVISIBLE + } + + fun setAnimationTints(darkMode: Boolean) { + if (darkMode) { + progressBar.progressTintList = ColorStateList.valueOf( + resources.getColor(R.color.desktop_mode_maximize_menu_progress_dark)) + maximizeWindow.background?.setTintList(ContextCompat.getColorStateList(context, + R.color.desktop_mode_caption_button_color_selector_dark)) + } else { + progressBar.progressTintList = ColorStateList.valueOf( + resources.getColor(R.color.desktop_mode_maximize_menu_progress_light)) + maximizeWindow.background?.setTintList(ContextCompat.getColorStateList(context, + R.color.desktop_mode_caption_button_color_selector_light)) + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt index 794b357c9f16..b82f7ca47ef3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.windowdecor +import android.annotation.IdRes import android.app.ActivityManager.RunningTaskInfo import android.content.Context import android.content.res.Resources @@ -27,6 +28,8 @@ import android.view.SurfaceControl import android.view.SurfaceControl.Transaction import android.view.SurfaceControlViewHost import android.view.View.OnClickListener +import android.view.View.OnGenericMotionListener +import android.view.View.OnTouchListener import android.view.WindowManager import android.view.WindowlessWindowManager import android.widget.Button @@ -49,6 +52,8 @@ class MaximizeMenu( private val displayController: DisplayController, private val taskInfo: RunningTaskInfo, private val onClickListener: OnClickListener, + private val onGenericMotionListener: OnGenericMotionListener, + private val onTouchListener: OnTouchListener, private val decorWindowContext: Context, private val menuPosition: PointF, private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() } @@ -142,15 +147,26 @@ class MaximizeMenu( private fun setupMaximizeMenu() { val maximizeMenuView = maximizeMenu?.mWindowViewHost?.view ?: return - maximizeMenuView.requireViewById<Button>( + maximizeMenuView.setOnGenericMotionListener(onGenericMotionListener) + maximizeMenuView.setOnTouchListener(onTouchListener) + + val maximizeButton = maximizeMenuView.requireViewById<Button>( R.id.maximize_menu_maximize_button - ).setOnClickListener(onClickListener) - maximizeMenuView.requireViewById<Button>( + ) + maximizeButton.setOnClickListener(onClickListener) + maximizeButton.setOnGenericMotionListener(onGenericMotionListener) + + val snapRightButton = maximizeMenuView.requireViewById<Button>( R.id.maximize_menu_snap_right_button - ).setOnClickListener(onClickListener) - maximizeMenuView.requireViewById<Button>( + ) + snapRightButton.setOnClickListener(onClickListener) + snapRightButton.setOnGenericMotionListener(onGenericMotionListener) + + val snapLeftButton = maximizeMenuView.requireViewById<Button>( R.id.maximize_menu_snap_left_button - ).setOnClickListener(onClickListener) + ) + snapLeftButton.setOnClickListener(onClickListener) + snapLeftButton.setOnGenericMotionListener(onGenericMotionListener) } /** @@ -173,4 +189,12 @@ class MaximizeMenu( private fun viewsLaidOut(): Boolean { return maximizeMenu?.mWindowViewHost?.view?.isLaidOut ?: false } + + companion object { + fun isMaximizeMenuView(@IdRes viewId: Int): Boolean { + return viewId == R.id.maximize_menu || viewId == R.id.maximize_menu_maximize_button || + viewId == R.id.maximize_menu_snap_left_button || + viewId == R.id.maximize_menu_snap_right_button + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt index 2309c54b6591..7e5b9bd649f2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt @@ -21,6 +21,7 @@ import com.android.internal.R.attr.materialColorSurfaceContainerHigh import com.android.internal.R.attr.materialColorSurfaceContainerLow import com.android.internal.R.attr.materialColorSurfaceDim import com.android.wm.shell.R +import com.android.wm.shell.windowdecor.MaximizeButtonView /** * A desktop mode window decoration used when the window is floating (i.e. freeform). It hosts @@ -32,8 +33,10 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( onCaptionTouchListener: View.OnTouchListener, onCaptionButtonClickListener: View.OnClickListener, onLongClickListener: OnLongClickListener, + onCaptionGenericMotionListener: View.OnGenericMotionListener, appName: CharSequence, - appIconBitmap: Bitmap + appIconBitmap: Bitmap, + onMaximizeHoverAnimationFinishedListener: () -> Unit ) : DesktopModeWindowDecorationViewHolder(rootView) { private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption) @@ -41,6 +44,8 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( private val openMenuButton: View = rootView.requireViewById(R.id.open_menu_button) private val closeWindowButton: ImageButton = rootView.requireViewById(R.id.close_window) private val expandMenuButton: ImageButton = rootView.requireViewById(R.id.expand_menu_button) + private val maximizeButtonView: MaximizeButtonView = + rootView.requireViewById(R.id.maximize_button_view) private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window) private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name) private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon) @@ -55,10 +60,13 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( closeWindowButton.setOnClickListener(onCaptionButtonClickListener) maximizeWindowButton.setOnClickListener(onCaptionButtonClickListener) maximizeWindowButton.setOnTouchListener(onCaptionTouchListener) + maximizeWindowButton.setOnGenericMotionListener(onCaptionGenericMotionListener) maximizeWindowButton.onLongClickListener = onLongClickListener closeWindowButton.setOnTouchListener(onCaptionTouchListener) appNameTextView.text = appName appIconImageView.setImageBitmap(appIconBitmap) + maximizeButtonView.onHoverAnimationFinishedListener = + onMaximizeHoverAnimationFinishedListener } override fun bindData(taskInfo: RunningTaskInfo) { @@ -73,12 +81,30 @@ internal class DesktopModeAppControlsWindowDecorationViewHolder( maximizeWindowButton.imageAlpha = alpha closeWindowButton.imageAlpha = alpha expandMenuButton.imageAlpha = alpha + + maximizeButtonView.setAnimationTints(isDarkMode()) } override fun onHandleMenuOpened() {} override fun onHandleMenuClosed() {} + fun setAnimatingTaskResize(animatingTaskResize: Boolean) { + // If animating a task resize, cancel any running hover animations + if (animatingTaskResize) { + maximizeButtonView.cancelHoverAnimation() + } + maximizeButtonView.hoverDisabled = animatingTaskResize + } + + fun onMaximizeWindowHoverExit() { + maximizeButtonView.cancelHoverAnimation() + } + + fun onMaximizeWindowHoverEnter() { + maximizeButtonView.startHoverAnimation() + } + @ColorInt private fun getCaptionBackgroundColor(taskInfo: RunningTaskInfo): Int { if (isTransparentBackgroundRequested(taskInfo)) { -- GitLab