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

Commit ba86a2a0 authored by mattsziklay's avatar mattsziklay
Browse files

Add handle menu to TYPE_STATUS_BAR_ADDITIONAL layer.

Changes handle menu's layer from TYPE_APPLICATION to
TYPE_STATUS_BAR_ADDITIONAL when the task is not in freeform. This allows
us to interact with the menu directly in status bar coordinates but
keeps it underneath notification shade in the case that shade is invoked
via a keyboard shortcut.

Splits AdditionalWindow into two child classes to distinguish between
AdditionalWindows that are hosted in this new layer and ones that use
the original method.

Additionally, modifies updateHandleMenuPillPositions to calculate
positions with global coordinates if needed.

Bug: 316186265
Test: Manual, open handle menu in different windowing modes
Test: atest HandleMenuTest
Flag: lse_desktop_experience.enable_additional_windows_above_status_bar
Change-Id: Ia91b2503dde3efbedd7999b1a6769378765f35bb
parent f6365408
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -439,7 +439,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
            } else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
                if (!decoration.isHandleMenuActive()) {
                    moveTaskToFront(decoration.mTaskInfo);
                    decoration.createHandleMenu();
                    decoration.createHandleMenu(mSplitScreenController);
                } else {
                    decoration.closeHandleMenu();
                }
+13 −2
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ import com.android.wm.shell.common.DisplayController;
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.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -650,7 +651,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
    /**
     * Create and display handle menu window.
     */
    void createHandleMenu() {
    void createHandleMenu(SplitScreenController splitScreenController) {
        loadAppInfoIfNeeded();
        mHandleMenu = new HandleMenu.Builder(this)
                .setAppIcon(mAppIconBitmap)
@@ -660,6 +661,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
                .setLayoutId(mRelayoutParams.mLayoutResId)
                .setWindowingButtonsVisible(DesktopModeStatus.canEnterDesktopMode(mContext))
                .setCaptionHeight(mResult.mCaptionHeight)
                .setDisplayController(mDisplayController)
                .setSplitScreenController(splitScreenController)
                .build();
        mWindowDecorViewHolder.onHandleMenuOpened();
        mHandleMenu.show();
@@ -815,11 +818,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
        // We want handle to remain pressed if the pointer moves outside of it during a drag.
        handle.setPressed((inHandle && action == ACTION_DOWN)
                || (handle.isPressed() && action != ACTION_UP && action != ACTION_CANCEL));
        if (isHandleMenuActive()) {
        if (isHandleMenuActive() && !isMenuAboveStatusBar()) {
            mHandleMenu.checkMotionEvent(ev);
        }
    }

    private boolean isMenuAboveStatusBar() {
        return Flags.enableAdditionalWindowsAboveStatusBar() && !mTaskInfo.isFreeform();
    }

    private boolean pointInView(View v, float x, float y) {
        return v != null && v.getLeft() <= x && v.getRight() >= x
                && v.getTop() <= y && v.getBottom() >= y;
@@ -868,6 +875,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
        return exclusionRegion;
    }

    int getCaptionX() {
        return mResult.mCaptionX;
    }

    @Override
    int getCaptionHeightId(@WindowingMode int windowingMode) {
        return getCaptionHeightIdStatic(windowingMode);
+97 −35
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;

import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
@@ -34,6 +36,7 @@ import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
@@ -42,7 +45,15 @@ import android.widget.ImageView;
import android.widget.TextView;
import android.window.SurfaceSyncGroup;

import androidx.annotation.VisibleForTesting;

import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;

/**
 * Handle menu opened when the appropriate button is clicked on.
@@ -56,15 +67,19 @@ class HandleMenu {
    private static final String TAG = "HandleMenu";
    private static final boolean SHOULD_SHOW_MORE_ACTIONS_PILL = false;
    private final Context mContext;
    private final WindowDecoration mParentDecor;
    private WindowDecoration.AdditionalWindow mHandleMenuWindow;
    private final PointF mHandleMenuPosition = new PointF();
    private final DesktopModeWindowDecoration mParentDecor;
    @VisibleForTesting
    AdditionalViewContainer mHandleMenuViewContainer;
    @VisibleForTesting
    final PointF mHandleMenuPosition = new PointF();
    private final boolean mShouldShowWindowingPill;
    private final Bitmap mAppIconBitmap;
    private final CharSequence mAppName;
    private final View.OnClickListener mOnClickListener;
    private final View.OnTouchListener mOnTouchListener;
    private final RunningTaskInfo mTaskInfo;
    private final DisplayController mDisplayController;
    private final SplitScreenController mSplitScreenController;
    private final int mLayoutResId;
    private int mMarginMenuTop;
    private int mMarginMenuStart;
@@ -74,12 +89,16 @@ class HandleMenu {
    private HandleMenuAnimator mHandleMenuAnimator;


    HandleMenu(WindowDecoration parentDecor, int layoutResId, View.OnClickListener onClickListener,
            View.OnTouchListener onTouchListener, Bitmap appIcon, CharSequence appName,
            boolean shouldShowWindowingPill, int captionHeight) {
    HandleMenu(DesktopModeWindowDecoration parentDecor, int layoutResId,
            View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
            Bitmap appIcon, CharSequence appName, DisplayController displayController,
            SplitScreenController splitScreenController, boolean shouldShowWindowingPill,
            int captionHeight) {
        mParentDecor = parentDecor;
        mContext = mParentDecor.mDecorWindowContext;
        mTaskInfo = mParentDecor.mTaskInfo;
        mDisplayController = displayController;
        mSplitScreenController = splitScreenController;
        mLayoutResId = layoutResId;
        mOnClickListener = onClickListener;
        mOnTouchListener = onTouchListener;
@@ -95,20 +114,27 @@ class HandleMenu {
        final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
        SurfaceControl.Transaction t = new SurfaceControl.Transaction();

        createHandleMenuWindow(t, ssg);
        createHandleMenuViewContainer(t, ssg);
        ssg.addTransaction(t);
        ssg.markSyncReady();
        setupHandleMenu();
        animateHandleMenu();
    }

    private void createHandleMenuWindow(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
    private void createHandleMenuViewContainer(SurfaceControl.Transaction t,
            SurfaceSyncGroup ssg) {
        final int x = (int) mHandleMenuPosition.x;
        final int y = (int) mHandleMenuPosition.y;
        mHandleMenuWindow = mParentDecor.addWindow(
        if (!mTaskInfo.isFreeform() && Flags.enableAdditionalWindowsAboveStatusBar()) {
            mHandleMenuViewContainer = new AdditionalSystemViewContainer(mContext,
                    R.layout.desktop_mode_window_decor_handle_menu, mTaskInfo.taskId,
                    x, y, mMenuWidth, mMenuHeight);
        } else {
            mHandleMenuViewContainer = mParentDecor.addWindow(
                    R.layout.desktop_mode_window_decor_handle_menu, "Handle Menu",
                    t, ssg, x, y, mMenuWidth, mMenuHeight);
        final View handleMenuView = mHandleMenuWindow.mWindowViewHost.getView();
        }
        final View handleMenuView = mHandleMenuViewContainer.getView();
        mHandleMenuAnimator = new HandleMenuAnimator(handleMenuView, mMenuWidth, mCaptionHeight);
    }

@@ -129,7 +155,7 @@ class HandleMenu {
     * pill.
     */
    private void setupHandleMenu() {
        final View handleMenu = mHandleMenuWindow.mWindowViewHost.getView();
        final View handleMenu = mHandleMenuViewContainer.getView();
        handleMenu.setOnTouchListener(mOnTouchListener);
        setupAppInfoPill(handleMenu);
        if (mShouldShowWindowingPill) {
@@ -147,6 +173,7 @@ class HandleMenu {
        final ImageView appIcon = handleMenu.findViewById(R.id.application_icon);
        final TextView appName = handleMenu.findViewById(R.id.application_name);
        collapseBtn.setOnClickListener(mOnClickListener);
        collapseBtn.setTaskInfo(mTaskInfo);
        appIcon.setImageBitmap(mAppIconBitmap);
        appName.setText(mAppName);
    }
@@ -215,32 +242,55 @@ class HandleMenu {
     * Updates handle menu's position variables to reflect its next position.
     */
    private void updateHandleMenuPillPositions() {
        final int menuX, menuY;
        final int captionWidth = mTaskInfo.getConfiguration()
                .windowConfiguration.getBounds().width();
        int menuX;
        final int menuY;
        if (mLayoutResId == R.layout.desktop_mode_app_header) {
            // Align the handle menu to the left of the caption.
            // Align the handle menu to the left side of the caption.
            menuX = mMarginMenuStart;
            menuY = mMarginMenuTop;
        } else {
            // Position the handle menu at the center of the caption.
            final int handleWidth = loadDimensionPixelSize(mContext.getResources(),
                    R.dimen.desktop_mode_fullscreen_decor_caption_width);
            final int handleOffset = (mMenuWidth / 2) - (handleWidth / 2);
            final int captionX = mParentDecor.getCaptionX();
            // TODO(b/343561161): This needs to be calculated differently if the task is in
            //  top/bottom split.
            if (Flags.enableAdditionalWindowsAboveStatusBar()) {
                final Rect leftOrTopStageBounds = new Rect();
                if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
                        == SPLIT_POSITION_BOTTOM_OR_RIGHT) {
                    mSplitScreenController.getStageBounds(leftOrTopStageBounds, new Rect());
                }
                // In a focused decor, we use global coordinates for handle menu. Therefore we
                // need to account for other factors like split stage and menu/handle width to
                // center the menu.
                final DisplayLayout layout = mDisplayController
                        .getDisplayLayout(mTaskInfo.displayId);
                menuX = captionX + handleOffset - (layout.width() / 2);
                if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
                        == SPLIT_POSITION_BOTTOM_OR_RIGHT && layout.isLandscape()) {
                    // If this task in the right stage, we need to offset by left stage's width
                    menuX += leftOrTopStageBounds.width();
                }
                menuY = mMarginMenuStart - ((layout.height() - mMenuHeight) / 2);
            } else {
                final int captionWidth = mTaskInfo.getConfiguration()
                        .windowConfiguration.getBounds().width();
                menuX = (captionWidth / 2) - (mMenuWidth / 2);
            menuY = mMarginMenuStart;
                menuY = mMarginMenuTop;
            }
        }

        // Handle Menu position setup.
        mHandleMenuPosition.set(menuX, menuY);

    }

    /**
     * Update pill layout, in case task changes have caused positioning to change.
     */
    void relayout(SurfaceControl.Transaction t) {
        if (mHandleMenuWindow != null) {
        if (mHandleMenuViewContainer != null) {
            updateHandleMenuPillPositions();
            t.setPosition(mHandleMenuWindow.mWindowSurface,
                    mHandleMenuPosition.x, mHandleMenuPosition.y);
            mHandleMenuViewContainer.setPosition(t, mHandleMenuPosition.x, mHandleMenuPosition.y);
        }
    }

@@ -252,7 +302,7 @@ class HandleMenu {
     * @param ev the MotionEvent to compare against.
     */
    void checkMotionEvent(MotionEvent ev) {
        final View handleMenu = mHandleMenuWindow.mWindowViewHost.getView();
        final View handleMenu = mHandleMenuViewContainer.getView();
        final HandleMenuImageButton collapse = handleMenu.findViewById(R.id.collapse_menu_button);
        final PointF inputPoint = translateInputToLocalSpace(ev);
        final boolean inputInCollapseButton = pointInView(collapse, inputPoint.x, inputPoint.y);
@@ -280,7 +330,7 @@ class HandleMenu {
    boolean isValidMenuInput(PointF inputPoint) {
        if (!viewsLaidOut()) return true;
        return pointInView(
                mHandleMenuWindow.mWindowViewHost.getView(),
                mHandleMenuViewContainer.getView(),
                inputPoint.x - mHandleMenuPosition.x,
                inputPoint.y - mHandleMenuPosition.y);
    }
@@ -294,7 +344,7 @@ class HandleMenu {
     * Check if the views for handle menu can be seen.
     */
    private boolean viewsLaidOut() {
        return mHandleMenuWindow.mWindowViewHost.getView().isLaidOut();
        return mHandleMenuViewContainer.getView().isLaidOut();
    }

    private void loadHandleMenuDimensions() {
@@ -333,8 +383,8 @@ class HandleMenu {

    void close() {
        final Runnable after = () -> {
            mHandleMenuWindow.releaseView();
            mHandleMenuWindow = null;
            mHandleMenuViewContainer.releaseView();
            mHandleMenuViewContainer = null;
        };
        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
                || mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
@@ -345,7 +395,7 @@ class HandleMenu {
    }

    static final class Builder {
        private final WindowDecoration mParent;
        private final DesktopModeWindowDecoration mParent;
        private CharSequence mName;
        private Bitmap mAppIcon;
        private View.OnClickListener mOnClickListener;
@@ -353,9 +403,10 @@ class HandleMenu {
        private int mLayoutId;
        private boolean mShowWindowingPill;
        private int mCaptionHeight;
        private DisplayController mDisplayController;
        private SplitScreenController mSplitScreenController;


        Builder(@NonNull WindowDecoration parent) {
        Builder(@NonNull DesktopModeWindowDecoration parent) {
            mParent = parent;
        }

@@ -394,9 +445,20 @@ class HandleMenu {
            return this;
        }

        Builder setDisplayController(DisplayController displayController) {
            mDisplayController = displayController;
            return this;
        }

        Builder setSplitScreenController(SplitScreenController splitScreenController) {
            mSplitScreenController = splitScreenController;
            return this;
        }

        HandleMenu build() {
            return new HandleMenu(mParent, mLayoutId, mOnClickListener, mOnTouchListener,
                    mAppIcon, mName, mShowWindowingPill, mCaptionHeight);
            return new HandleMenu(mParent, mLayoutId, mOnClickListener,
                    mOnTouchListener, mAppIcon, mName, mDisplayController, mSplitScreenController,
                    mShowWindowingPill, mCaptionHeight);
        }
    }
}
+17 −3
Original line number Diff line number Diff line
@@ -15,6 +15,10 @@
 */
package com.android.wm.shell.windowdecor

import android.app.ActivityManager.RunningTaskInfo
import com.android.window.flags.Flags
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer

import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
@@ -25,10 +29,20 @@ import android.widget.ImageButton
 * 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.
 * TODO(b/342229481): Remove this and all uses of it once [AdditionalSystemViewContainer] is no longer
 *  guarded by a flag.
 */
class HandleMenuImageButton(context: Context?, attrs: AttributeSet?) :
    ImageButton(context, attrs) {
class HandleMenuImageButton(
    context: Context?,
    attrs: AttributeSet?
) : ImageButton(context, attrs) {
    lateinit var taskInfo: RunningTaskInfo

    override fun onHoverEvent(motionEvent: MotionEvent): Boolean {
        if (Flags.enableAdditionalWindowsAboveStatusBar() || taskInfo.isFreeform) {
            return super.onHoverEvent(motionEvent)
        } else {
            return false
        }
    }
}
+7 −7
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.windowdecor.WindowDecoration.AdditionalWindow
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
import java.util.function.Supplier


@@ -70,7 +70,7 @@ class MaximizeMenu(
        private val menuPosition: PointF,
        private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }
) {
    private var maximizeMenu: AdditionalWindow? = null
    private var maximizeMenu: AdditionalViewHostViewContainer? = null
    private lateinit var viewHost: SurfaceControlViewHost
    private lateinit var leash: SurfaceControl
    private val openMenuAnimatorSet = AnimatorSet()
@@ -145,7 +145,8 @@ class MaximizeMenu(
                .setPosition(leash, menuPosition.x, menuPosition.y)
                .setCornerRadius(leash, cornerRadius)
                .show(leash)
        maximizeMenu = AdditionalWindow(leash, viewHost, transactionSupplier)
        maximizeMenu =
            AdditionalViewHostViewContainer(leash, viewHost, transactionSupplier)

        syncQueue.runInSync { transaction ->
            transaction.merge(t)
@@ -154,8 +155,7 @@ class MaximizeMenu(
    }

    private fun animateOpenMenu() {
        val viewHost = maximizeMenu?.mWindowViewHost
        val maximizeMenuView = viewHost?.view ?: return
        val maximizeMenuView = maximizeMenu?.view ?: return
        val maximizeWindowText = maximizeMenuView.requireViewById<TextView>(
                R.id.maximize_menu_maximize_window_text)
        val snapWindowText = maximizeMenuView.requireViewById<TextView>(
@@ -233,7 +233,7 @@ class MaximizeMenu(
    }

    private fun setupMaximizeMenu() {
        val maximizeMenuView = maximizeMenu?.mWindowViewHost?.view ?: return
        val maximizeMenuView = maximizeMenu?.view ?: return

        maximizeMenuView.setOnGenericMotionListener(onGenericMotionListener)
        maximizeMenuView.setOnTouchListener(onTouchListener)
@@ -275,7 +275,7 @@ class MaximizeMenu(
     * Check if the views for maximize menu can be seen.
     */
    private fun viewsLaidOut(): Boolean {
        return maximizeMenu?.mWindowViewHost?.view?.isLaidOut ?: false
        return maximizeMenu?.view?.isLaidOut ?: false
    }

    fun onMaximizeMenuHoverEnter(viewId: Int, ev: MotionEvent) {
Loading