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

Commit 8a385328 authored by Jorge Gil's avatar Jorge Gil Committed by Android (Google) Code Review
Browse files

Merge "[1/N] (Max Menu) Better encapsulation of window decor's views" into main

parents 6fa985a0 417094ee
Loading
Loading
Loading
Loading
+79 −67
Original line number Diff line number Diff line
@@ -14,18 +14,22 @@
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:androidprv="http://schemas.android.com/apk/prv/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"
    android:orientation="horizontal"
    android:gravity="center"
    android:padding="16dp"
    android:background="@drawable/desktop_mode_maximize_menu_background"
    android:elevation="1dp">

    <LinearLayout
        android:id="@+id/container"
        android:layout_width="@dimen/desktop_mode_maximize_menu_width"
        android:layout_height="@dimen/desktop_mode_maximize_menu_height"
        android:orientation="horizontal"
        android:padding="16dp"
        android:gravity="center">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
@@ -99,3 +103,11 @@
        </LinearLayout>
    </LinearLayout>

    <!-- Empty view intentionally placed in front of everything else and matching the menu size
     used to monitor input events over the entire menu. -->
    <View
        android:id="@+id/maximize_menu_overlay"
        android:layout_width="@dimen/desktop_mode_maximize_menu_width"
        android:layout_height="@dimen/desktop_mode_maximize_menu_height"/>
</FrameLayout>
+52 −67
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
import static android.view.MotionEvent.ACTION_HOVER_EXIT;
import static android.view.MotionEvent.ACTION_HOVER_MOVE;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
@@ -103,6 +102,7 @@ import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;

import java.io.PrintWriter;
import java.util.Objects;
@@ -383,10 +383,32 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
        mWindowDecorByTaskId.remove(taskInfo.taskId);
    }

    private void onMaximizeOrRestore(int taskId, String tag) {
        final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
        if (decoration == null) {
            return;
        }
        InteractionJankMonitorUtils.beginTracing(
                Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, mContext, decoration.mTaskSurface, tag);
        mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
        decoration.closeHandleMenu();
        decoration.closeMaximizeMenu();
    }

    private void onSnapResize(int taskId, boolean left) {
        final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
        if (decoration == null) {
            return;
        }
        mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo,
                left ? SnapPosition.LEFT : SnapPosition.RIGHT);
        decoration.closeHandleMenu();
        decoration.closeMaximizeMenu();
    }

    private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
            implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
            View.OnGenericMotionListener, DragDetector.MotionEventHandler {
        private static final int CLOSE_MAXIMIZE_MENU_DELAY_MS = 150;

        private final int mTaskId;
        private final WindowContainerToken mTaskToken;
@@ -405,7 +427,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
        private boolean mTouchscreenInUse;
        private boolean mHasLongClicked;
        private int mDragPointerId = -1;
        private final Runnable mCloseMaximizeWindowRunnable;

        private DesktopModeTouchEventListener(
                RunningTaskInfo taskInfo,
@@ -416,11 +437,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            mDragDetector = new DragDetector(this);
            mGestureDetector = new GestureDetector(mContext, this);
            mDisplayId = taskInfo.displayId;
            mCloseMaximizeWindowRunnable = () -> {
                final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
                if (decoration == null) return;
                decoration.closeMaximizeMenu();
            };
        }

        @Override
@@ -472,31 +488,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            } else if (id == R.id.collapse_menu_button) {
                decoration.closeHandleMenu();
            } else if (id == R.id.maximize_window) {
                InteractionJankMonitorUtils.beginTracing(
                        Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, /* view= */ v,
                        /* tag= */ "caption_bar_button");
                final RunningTaskInfo taskInfo = decoration.mTaskInfo;
                decoration.closeHandleMenu();
                decoration.closeMaximizeMenu();
                mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
            } else if (id == R.id.maximize_menu_maximize_button) {
                InteractionJankMonitorUtils.beginTracing(
                        Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, /* view= */ v,
                        /* tag= */ "maximize_menu_option");
                final RunningTaskInfo taskInfo = decoration.mTaskInfo;
                mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
                decoration.closeHandleMenu();
                decoration.closeMaximizeMenu();
            } else if (id == R.id.maximize_menu_snap_left_button) {
                final RunningTaskInfo taskInfo = decoration.mTaskInfo;
                mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.LEFT);
                decoration.closeHandleMenu();
                decoration.closeMaximizeMenu();
            } else if (id == R.id.maximize_menu_snap_right_button) {
                final RunningTaskInfo taskInfo = decoration.mTaskInfo;
                mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.RIGHT);
                decoration.closeHandleMenu();
                decoration.closeMaximizeMenu();
                // TODO(b/346441962): move click detection logic into the decor's
                //  {@link AppHeaderViewHolder}. Let it encapsulate the that and have it report
                //  back to the decoration using
                //  {@link DesktopModeWindowDecoration#setOnMaximizeOrRestoreClickListener}, which
                //  should shared with the maximize menu's maximize/restore actions.
                onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button");
            }
        }

@@ -578,40 +575,26 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            return false;
        }

        /**
         * TODO(b/346441962): move this hover detection logic into the decor's
         * {@link AppHeaderViewHolder}.
         */
        @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);
                    if (id != R.id.maximize_window) {
                        decoration.onMaximizeMenuHoverEnter(id, ev);
                    }
            if (ev.getAction() == ACTION_HOVER_ENTER && id == R.id.maximize_window) {
                decoration.setAppHeaderMaximizeButtonHovered(true);
                if (!decoration.isMaximizeMenuActive()) {
                    decoration.onMaximizeButtonHoverEnter();
                }
                return true;
            } else if (ev.getAction() == ACTION_HOVER_MOVE
                    && MaximizeMenu.Companion.isMaximizeMenuView(id)) {
                decoration.onMaximizeMenuHoverMove(id, ev);
                mMainHandler.removeCallbacks(mCloseMaximizeWindowRunnable);
            } 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);
                    if (id != R.id.maximize_window) {
                        decoration.onMaximizeMenuHoverExit(id, ev);
            }
            if (ev.getAction() == ACTION_HOVER_EXIT && id == R.id.maximize_window) {
                decoration.setAppHeaderMaximizeButtonHovered(false);
                decoration.onMaximizeHoverStateChanged();
                if (!decoration.isMaximizeMenuActive()) {
                    decoration.onMaximizeButtonHoverExit();
                }
                return true;
            }
@@ -719,11 +702,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
                    && action != MotionEvent.ACTION_CANCEL)) {
                return false;
            }
            final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
            InteractionJankMonitorUtils.beginTracing(
                    Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, mContext,
                    /* surface= */ decoration.mTaskSurface, /* tag= */ "double_tap");
            mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
            onMaximizeOrRestore(mTaskId, "double_tap");
            return true;
        }
    }
@@ -1105,7 +1084,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {

        final DesktopModeTouchEventListener touchEventListener =
                new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);

        windowDecoration.setOnMaximizeOrRestoreClickListener(this::onMaximizeOrRestore);
        windowDecoration.setOnLeftSnapClickListener((taskId, tag) -> {
            onSnapResize(taskId, true /* isLeft */);
        });
        windowDecoration.setOnRightSnapClickListener((taskId, tag) -> {
            onSnapResize(taskId, false /* isLeft */);
        });
        windowDecoration.setCaptionListeners(
                touchEventListener, touchEventListener, touchEventListener, touchEventListener);
        windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
+88 −26
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -87,6 +88,9 @@ import java.util.function.Supplier;
public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
    private static final String TAG = "DesktopModeWindowDecoration";

    @VisibleForTesting
    static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L;

    private final Handler mHandler;
    private final Choreographer mChoreographer;
    private final SyncTransactionQueue mSyncQueue;
@@ -96,6 +100,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
    private View.OnTouchListener mOnCaptionTouchListener;
    private View.OnLongClickListener mOnCaptionLongClickListener;
    private View.OnGenericMotionListener mOnCaptionGenericMotionListener;
    private OnTaskActionClickListener mOnMaximizeOrRestoreClickListener;
    private OnTaskActionClickListener mOnLeftSnapClickListener;
    private OnTaskActionClickListener mOnRightSnapClickListener;
    private DragPositioningCallback mDragPositioningCallback;
    private DragResizeInputListener mDragResizeListener;
    private DragDetector mDragDetector;
@@ -120,6 +127,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
    private ExclusionRegionListener mExclusionRegionListener;

    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
    private final MaximizeMenuFactory mMaximizeMenuFactory;

    // Hover state for the maximize menu and button. The menu will remain open as long as either of
    // these is true. See {@link #onMaximizeHoverStateChanged()}.
    private boolean mIsAppHeaderMaximizeButtonHovered = false;
    private boolean mIsMaximizeMenuHovered = false;
    // Used to schedule the closing of the maximize menu when neither of the button or menu are
    // being hovered. There's a small delay after stopping the hover, to allow a quick reentry
    // to cancel the close.
    private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu;

    DesktopModeWindowDecoration(
            Context context,
@@ -135,7 +152,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
                handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
                SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
                WindowContainerTransaction::new, SurfaceControl::new,
                new SurfaceControlViewHostFactory() {});
                new SurfaceControlViewHostFactory() {},
                DefaultMaximizeMenuFactory.INSTANCE);
    }

    DesktopModeWindowDecoration(
@@ -152,7 +170,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
            Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
            Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
            Supplier<SurfaceControl> surfaceControlSupplier,
            SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
            SurfaceControlViewHostFactory surfaceControlViewHostFactory,
            MaximizeMenuFactory maximizeMenuFactory) {
        super(context, displayController, taskOrganizer, taskInfo, taskSurface,
                surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
                windowContainerTransactionSupplier, surfaceControlSupplier,
@@ -161,6 +180,31 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
        mChoreographer = choreographer;
        mSyncQueue = syncQueue;
        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
        mMaximizeMenuFactory = maximizeMenuFactory;
    }

    /**
     * Register a listener to be called back when one of the tasks' maximize/restore action is
     * triggered.
     * TODO(b/346441962): hook this up to double-tap and the header's maximize button, instead of
     *  having the ViewModel deal with parsing motion events.
     */
    void setOnMaximizeOrRestoreClickListener(OnTaskActionClickListener listener) {
        mOnMaximizeOrRestoreClickListener = listener;
    }

    /**
     * Register a listener to be called back when one of the tasks snap-left action is triggered.
     */
    void setOnLeftSnapClickListener(OnTaskActionClickListener listener) {
        mOnLeftSnapClickListener = listener;
    }

    /**
     * Register a listener to be called back when one of the tasks' snap-right action is triggered.
     */
    void setOnRightSnapClickListener(OnTaskActionClickListener listener) {
        mOnRightSnapClickListener = listener;
    }

    void setCaptionListeners(
@@ -714,11 +758,41 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
     * Create and display maximize menu window
     */
    void createMaximizeMenu() {
        mMaximizeMenu = new MaximizeMenu(mSyncQueue, mRootTaskDisplayAreaOrganizer,
                mDisplayController, mTaskInfo, mOnCaptionButtonClickListener,
                mOnCaptionGenericMotionListener, mOnCaptionTouchListener, mContext,
        mMaximizeMenu = mMaximizeMenuFactory.create(mSyncQueue, mRootTaskDisplayAreaOrganizer,
                mDisplayController, mTaskInfo, mContext,
                calculateMaximizeMenuPosition(), mSurfaceControlTransactionSupplier);
        mMaximizeMenu.show();
        mMaximizeMenu.show(
                mOnMaximizeOrRestoreClickListener,
                mOnLeftSnapClickListener,
                mOnRightSnapClickListener,
                hovered -> {
                    mIsMaximizeMenuHovered = hovered;
                    onMaximizeHoverStateChanged();
                    return null;
                }
        );
    }

    /** Set whether the app header's maximize button is hovered. */
    void setAppHeaderMaximizeButtonHovered(boolean hovered) {
        mIsAppHeaderMaximizeButtonHovered = hovered;
        onMaximizeHoverStateChanged();
    }

    /**
     * Called when either one of the maximize button in the app header or the maximize menu has
     * changed its hover state.
     */
    void onMaximizeHoverStateChanged() {
        if (!mIsMaximizeMenuHovered && !mIsAppHeaderMaximizeButtonHovered) {
            // Neither is hovered, close the menu.
            if (isMaximizeMenuActive()) {
                mHandler.postDelayed(mCloseMaximizeWindowRunnable, CLOSE_MAXIMIZE_MENU_DELAY_MS);
            }
            return;
        }
        // At least one of the two is hovered, cancel the close if needed.
        mHandler.removeCallbacks(mCloseMaximizeWindowRunnable);
    }

    /**
@@ -992,34 +1066,22 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
                .setAnimatingTaskResize(animatingTaskResize);
    }

    /** Called when there is a {@Link ACTION_HOVER_EXIT} on the maximize window button. */
    void onMaximizeWindowHoverExit() {
    /**
     * Called when there is a {@link MotionEvent#ACTION_HOVER_EXIT} on the maximize window button.
     */
    void onMaximizeButtonHoverExit() {
        ((AppHeaderViewHolder) mWindowDecorViewHolder)
                .onMaximizeWindowHoverExit();
    }

    /** Called when there is a {@Link ACTION_HOVER_ENTER} on the maximize window button. */
    void onMaximizeWindowHoverEnter() {
    /**
     * Called when there is a {@link MotionEvent#ACTION_HOVER_ENTER} on the maximize window button.
     */
    void onMaximizeButtonHoverEnter() {
        ((AppHeaderViewHolder) mWindowDecorViewHolder)
                .onMaximizeWindowHoverEnter();
    }

    /** Called when there is a {@Link ACTION_HOVER_ENTER} on a view in the maximize menu. */
    void onMaximizeMenuHoverEnter(int id, MotionEvent ev) {
        mMaximizeMenu.onMaximizeMenuHoverEnter(id, ev);
    }

    /** Called when there is a {@Link ACTION_HOVER_MOVE} on a view in the maximize menu. */
    void onMaximizeMenuHoverMove(int id, MotionEvent ev) {
        mMaximizeMenu.onMaximizeMenuHoverMove(id, ev);
    }

    /** Called when there is a {@Link ACTION_HOVER_EXIT} on a view in the maximize menu. */
    void onMaximizeMenuHoverExit(int id, MotionEvent ev) {
        mMaximizeMenu.onMaximizeMenuHoverExit(id, ev);
    }


    @Override
    public String toString() {
        return "{"
+128 −97

File changed.

Preview size limit exceeded, changes collapsed.

+27 −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.common

/** A callback to be invoked when a Task's window decor element is clicked. */
fun interface OnTaskActionClickListener {
    /**
     * Called when a task's decor element has been clicked.
     *
     * @param taskId the id of the task.
     * @param tag a readable identifier for the element.
     */
    fun onClick(taskId: Int, tag: String)
}
Loading