Loading libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml +1 −1 Original line number Diff line number Diff line Loading @@ -52,7 +52,7 @@ android:textStyle="normal" android:layout_weight="1"/> <ImageButton <com.android.wm.shell.windowdecor.HandleMenuImageButton android:id="@+id/collapse_menu_button" android:layout_width="32dp" android:layout_height="32dp" Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +33 −22 Original line number Diff line number Diff line Loading @@ -795,7 +795,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (relevantDecor == null || relevantDecor.checkTouchEventInCaption(ev)) { return; } relevantDecor.updateHoverAndPressStatus(ev); final int action = ev.getActionMasked(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { if (!mTransitionDragActive) { Loading @@ -812,10 +812,24 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { */ private void handleCaptionThroughStatusBar(MotionEvent ev, DesktopModeWindowDecoration relevantDecor) { if (relevantDecor == null) { if (ev.getActionMasked() == ACTION_UP) { mMoveToDesktopAnimator = null; mTransitionDragActive = false; } return; } switch (ev.getActionMasked()) { case MotionEvent.ACTION_HOVER_EXIT: case MotionEvent.ACTION_HOVER_MOVE: case MotionEvent.ACTION_HOVER_ENTER: { relevantDecor.updateHoverAndPressStatus(ev); break; } case MotionEvent.ACTION_DOWN: { // Begin drag through status bar if applicable. if (relevantDecor != null) { relevantDecor.checkTouchEvent(ev); relevantDecor.updateHoverAndPressStatus(ev); mDragToDesktopAnimationStartBounds.set( relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds()); boolean dragFromStatusBarAllowed = false; Loading @@ -831,15 +845,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)) { mTransitionDragActive = true; } } break; } case MotionEvent.ACTION_UP: { if (relevantDecor == null) { mMoveToDesktopAnimator = null; mTransitionDragActive = false; return; } if (mTransitionDragActive) { final DesktopModeVisualIndicator.IndicatorType indicatorType = mDesktopTasksController.updateVisualIndicator(relevantDecor.mTaskInfo, Loading @@ -854,6 +862,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mMoveToDesktopAnimator = null; return; } else if (mMoveToDesktopAnimator != null) { // Though this isn't a hover event, we need to update handle's hover state // as it likely will change. relevantDecor.updateHoverAndPressStatus(ev); mDesktopTasksController.onDragPositioningEndThroughStatusBar( new PointF(ev.getRawX(), ev.getRawY()), relevantDecor.mTaskInfo, Loading @@ -866,7 +877,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDesktopTasksController.releaseVisualIndicator(); } } relevantDecor.checkClickEvent(ev); relevantDecor.checkTouchEvent(ev); break; } Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +34 −11 Original line number Diff line number Diff line Loading @@ -19,6 +19,8 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.windowingModeToString; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT; Loading Loading @@ -713,27 +715,48 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } /** * Check a passed MotionEvent if a click has occurred on any button on this caption * Check a passed MotionEvent if it has occurred on any button related to this decor. * Note this should only be called when a regular onClick is not possible * (i.e. the button was clicked through status bar layer) * * @param ev the MotionEvent to compare */ void checkClickEvent(MotionEvent ev) { void checkTouchEvent(MotionEvent ev) { if (mResult.mRootView == null) return; if (!isHandleMenuActive()) { // Click if point in caption handle view final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); final View handle = caption.findViewById(R.id.caption_handle); if (checkTouchEventInFocusedCaptionHandle(ev)) { mOnCaptionButtonClickListener.onClick(handle); final boolean inHandle = !isHandleMenuActive() && checkTouchEventInFocusedCaptionHandle(ev); final int action = ev.getActionMasked(); if (action == ACTION_UP && inHandle) { handle.performClick(); } } else { mHandleMenu.checkClickEvent(ev); if (isHandleMenuActive()) { mHandleMenu.checkMotionEvent(ev); closeHandleMenuIfNeeded(ev); } } /** * Updates hover and pressed status of views in this decoration. Should only be called * when status cannot be updated normally (i.e. the button is hovered through status * bar layer). * @param ev the MotionEvent to compare against. */ void updateHoverAndPressStatus(MotionEvent ev) { if (mResult.mRootView == null) return; final View handle = mResult.mRootView.findViewById(R.id.caption_handle); final boolean inHandle = !isHandleMenuActive() && checkTouchEventInFocusedCaptionHandle(ev); final int action = ev.getActionMasked(); // The comparison against ACTION_UP is needed for the cancel drag to desktop case. handle.setHovered(inHandle && action != ACTION_UP); handle.setPressed(inHandle && action == ACTION_DOWN); if (isHandleMenuActive()) { mHandleMenu.checkMotionEvent(ev); } } private boolean pointInView(View v, float x, float y) { return v != null && v.getLeft() <= x && v.getRight() >= x && v.getTop() <= y && v.getBottom() >= y; Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java +23 −13 Original line number Diff line number Diff line Loading @@ -20,6 +20,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 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.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; import android.annotation.NonNull; import android.annotation.Nullable; Loading Loading @@ -140,7 +142,8 @@ class HandleMenu { * Set up interactive elements of handle menu's app info pill. */ private void setupAppInfoPill(View handleMenu) { final ImageButton collapseBtn = handleMenu.findViewById(R.id.collapse_menu_button); final HandleMenuImageButton collapseBtn = handleMenu.findViewById(R.id.collapse_menu_button); final ImageView appIcon = handleMenu.findViewById(R.id.application_icon); final TextView appName = handleMenu.findViewById(R.id.application_name); collapseBtn.setOnClickListener(mOnClickListener); Loading Loading @@ -243,22 +246,29 @@ class HandleMenu { } /** * Check a passed MotionEvent if a click has occurred on any button on this caption * Note this should only be called when a regular onClick is not possible * Check a passed MotionEvent if a click or hover has occurred on any button on this caption * Note this should only be called when a regular onClick/onHover is not possible * (i.e. the button was clicked through status bar layer) * * @param ev the MotionEvent to compare against. */ void checkClickEvent(MotionEvent ev) { void checkMotionEvent(MotionEvent ev) { final View handleMenu = mHandleMenuWindow.mWindowViewHost.getView(); final ImageButton collapse = handleMenu.findViewById(R.id.collapse_menu_button); // Translate the input point from display coordinates to the same space as the collapse // button, meaning its parent (app info pill view). final PointF inputPoint = new PointF(ev.getX() - mHandleMenuPosition.x, ev.getY() - mHandleMenuPosition.y); if (pointInView(collapse, inputPoint.x, inputPoint.y)) { mOnClickListener.onClick(collapse); final HandleMenuImageButton collapse = handleMenu.findViewById(R.id.collapse_menu_button); final PointF inputPoint = translateInputToLocalSpace(ev); final boolean inputInCollapseButton = pointInView(collapse, inputPoint.x, inputPoint.y); final int action = ev.getActionMasked(); collapse.setHovered(inputInCollapseButton && action != ACTION_UP); collapse.setPressed(inputInCollapseButton && action == ACTION_DOWN); if (action == ACTION_UP && inputInCollapseButton) { collapse.performClick(); } } // Translate the input point from display coordinates to the same space as the handle menu. private PointF translateInputToLocalSpace(MotionEvent ev) { return new PointF(ev.getX() - mHandleMenuPosition.x, ev.getY() - mHandleMenuPosition.y); } /** Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt 0 → 100644 +34 −0 Original line number Diff line number Diff line /* * 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.content.Context import android.util.AttributeSet import android.view.MotionEvent import android.widget.ImageButton /** * A custom [ImageButton] that intentionally does not handle hover events. * This is due to the hover events being handled by [DesktopModeWindowDecorViewModel] * in order to take the status bar layer into account. Handling it in both classes results in a * flicker when the hover moves from outside to inside status bar layer. */ class HandleMenuImageButton(context: Context?, attrs: AttributeSet?) : ImageButton(context, attrs) { override fun onHoverEvent(motionEvent: MotionEvent): Boolean { return false } } Loading
libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml +1 −1 Original line number Diff line number Diff line Loading @@ -52,7 +52,7 @@ android:textStyle="normal" android:layout_weight="1"/> <ImageButton <com.android.wm.shell.windowdecor.HandleMenuImageButton android:id="@+id/collapse_menu_button" android:layout_width="32dp" android:layout_height="32dp" Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +33 −22 Original line number Diff line number Diff line Loading @@ -795,7 +795,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { if (relevantDecor == null || relevantDecor.checkTouchEventInCaption(ev)) { return; } relevantDecor.updateHoverAndPressStatus(ev); final int action = ev.getActionMasked(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { if (!mTransitionDragActive) { Loading @@ -812,10 +812,24 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { */ private void handleCaptionThroughStatusBar(MotionEvent ev, DesktopModeWindowDecoration relevantDecor) { if (relevantDecor == null) { if (ev.getActionMasked() == ACTION_UP) { mMoveToDesktopAnimator = null; mTransitionDragActive = false; } return; } switch (ev.getActionMasked()) { case MotionEvent.ACTION_HOVER_EXIT: case MotionEvent.ACTION_HOVER_MOVE: case MotionEvent.ACTION_HOVER_ENTER: { relevantDecor.updateHoverAndPressStatus(ev); break; } case MotionEvent.ACTION_DOWN: { // Begin drag through status bar if applicable. if (relevantDecor != null) { relevantDecor.checkTouchEvent(ev); relevantDecor.updateHoverAndPressStatus(ev); mDragToDesktopAnimationStartBounds.set( relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds()); boolean dragFromStatusBarAllowed = false; Loading @@ -831,15 +845,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)) { mTransitionDragActive = true; } } break; } case MotionEvent.ACTION_UP: { if (relevantDecor == null) { mMoveToDesktopAnimator = null; mTransitionDragActive = false; return; } if (mTransitionDragActive) { final DesktopModeVisualIndicator.IndicatorType indicatorType = mDesktopTasksController.updateVisualIndicator(relevantDecor.mTaskInfo, Loading @@ -854,6 +862,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mMoveToDesktopAnimator = null; return; } else if (mMoveToDesktopAnimator != null) { // Though this isn't a hover event, we need to update handle's hover state // as it likely will change. relevantDecor.updateHoverAndPressStatus(ev); mDesktopTasksController.onDragPositioningEndThroughStatusBar( new PointF(ev.getRawX(), ev.getRawY()), relevantDecor.mTaskInfo, Loading @@ -866,7 +877,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDesktopTasksController.releaseVisualIndicator(); } } relevantDecor.checkClickEvent(ev); relevantDecor.checkTouchEvent(ev); break; } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +34 −11 Original line number Diff line number Diff line Loading @@ -19,6 +19,8 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.windowingModeToString; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT; Loading Loading @@ -713,27 +715,48 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } /** * Check a passed MotionEvent if a click has occurred on any button on this caption * Check a passed MotionEvent if it has occurred on any button related to this decor. * Note this should only be called when a regular onClick is not possible * (i.e. the button was clicked through status bar layer) * * @param ev the MotionEvent to compare */ void checkClickEvent(MotionEvent ev) { void checkTouchEvent(MotionEvent ev) { if (mResult.mRootView == null) return; if (!isHandleMenuActive()) { // Click if point in caption handle view final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption); final View handle = caption.findViewById(R.id.caption_handle); if (checkTouchEventInFocusedCaptionHandle(ev)) { mOnCaptionButtonClickListener.onClick(handle); final boolean inHandle = !isHandleMenuActive() && checkTouchEventInFocusedCaptionHandle(ev); final int action = ev.getActionMasked(); if (action == ACTION_UP && inHandle) { handle.performClick(); } } else { mHandleMenu.checkClickEvent(ev); if (isHandleMenuActive()) { mHandleMenu.checkMotionEvent(ev); closeHandleMenuIfNeeded(ev); } } /** * Updates hover and pressed status of views in this decoration. Should only be called * when status cannot be updated normally (i.e. the button is hovered through status * bar layer). * @param ev the MotionEvent to compare against. */ void updateHoverAndPressStatus(MotionEvent ev) { if (mResult.mRootView == null) return; final View handle = mResult.mRootView.findViewById(R.id.caption_handle); final boolean inHandle = !isHandleMenuActive() && checkTouchEventInFocusedCaptionHandle(ev); final int action = ev.getActionMasked(); // The comparison against ACTION_UP is needed for the cancel drag to desktop case. handle.setHovered(inHandle && action != ACTION_UP); handle.setPressed(inHandle && action == ACTION_DOWN); if (isHandleMenuActive()) { mHandleMenu.checkMotionEvent(ev); } } private boolean pointInView(View v, float x, float y) { return v != null && v.getLeft() <= x && v.getRight() >= x && v.getTop() <= y && v.getBottom() >= y; Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java +23 −13 Original line number Diff line number Diff line Loading @@ -20,6 +20,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 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.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; import android.annotation.NonNull; import android.annotation.Nullable; Loading Loading @@ -140,7 +142,8 @@ class HandleMenu { * Set up interactive elements of handle menu's app info pill. */ private void setupAppInfoPill(View handleMenu) { final ImageButton collapseBtn = handleMenu.findViewById(R.id.collapse_menu_button); final HandleMenuImageButton collapseBtn = handleMenu.findViewById(R.id.collapse_menu_button); final ImageView appIcon = handleMenu.findViewById(R.id.application_icon); final TextView appName = handleMenu.findViewById(R.id.application_name); collapseBtn.setOnClickListener(mOnClickListener); Loading Loading @@ -243,22 +246,29 @@ class HandleMenu { } /** * Check a passed MotionEvent if a click has occurred on any button on this caption * Note this should only be called when a regular onClick is not possible * Check a passed MotionEvent if a click or hover has occurred on any button on this caption * Note this should only be called when a regular onClick/onHover is not possible * (i.e. the button was clicked through status bar layer) * * @param ev the MotionEvent to compare against. */ void checkClickEvent(MotionEvent ev) { void checkMotionEvent(MotionEvent ev) { final View handleMenu = mHandleMenuWindow.mWindowViewHost.getView(); final ImageButton collapse = handleMenu.findViewById(R.id.collapse_menu_button); // Translate the input point from display coordinates to the same space as the collapse // button, meaning its parent (app info pill view). final PointF inputPoint = new PointF(ev.getX() - mHandleMenuPosition.x, ev.getY() - mHandleMenuPosition.y); if (pointInView(collapse, inputPoint.x, inputPoint.y)) { mOnClickListener.onClick(collapse); final HandleMenuImageButton collapse = handleMenu.findViewById(R.id.collapse_menu_button); final PointF inputPoint = translateInputToLocalSpace(ev); final boolean inputInCollapseButton = pointInView(collapse, inputPoint.x, inputPoint.y); final int action = ev.getActionMasked(); collapse.setHovered(inputInCollapseButton && action != ACTION_UP); collapse.setPressed(inputInCollapseButton && action == ACTION_DOWN); if (action == ACTION_UP && inputInCollapseButton) { collapse.performClick(); } } // Translate the input point from display coordinates to the same space as the handle menu. private PointF translateInputToLocalSpace(MotionEvent ev) { return new PointF(ev.getX() - mHandleMenuPosition.x, ev.getY() - mHandleMenuPosition.y); } /** Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuImageButton.kt 0 → 100644 +34 −0 Original line number Diff line number Diff line /* * 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.content.Context import android.util.AttributeSet import android.view.MotionEvent import android.widget.ImageButton /** * A custom [ImageButton] that intentionally does not handle hover events. * This is due to the hover events being handled by [DesktopModeWindowDecorViewModel] * in order to take the status bar layer into account. Handling it in both classes results in a * flicker when the hover moves from outside to inside status bar layer. */ class HandleMenuImageButton(context: Context?, attrs: AttributeSet?) : ImageButton(context, attrs) { override fun onHoverEvent(motionEvent: MotionEvent): Boolean { return false } }