Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3b463a51 authored by Matt Sziklay's avatar Matt Sziklay Committed by Android (Google) Code Review
Browse files

Merge "Allow hovering/pressing handle elements through status bar." into main

parents a6402d76 1d2cef78
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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"
+33 −22
Original line number Diff line number Diff line
@@ -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) {
@@ -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;
@@ -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,
@@ -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,
@@ -866,7 +877,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                        mDesktopTasksController.releaseVisualIndicator();
                    }
                }
                relevantDecor.checkClickEvent(ev);
                relevantDecor.checkTouchEvent(ev);
                break;
            }

+34 −11
Original line number Diff line number Diff line
@@ -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;

@@ -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;
+23 −13
Original line number Diff line number Diff line
@@ -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;
@@ -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);
@@ -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);
    }

    /**
+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
    }
}