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

Commit 2625245f authored by mattsziklay's avatar mattsziklay
Browse files

Enable interacting with caption through status bar.

Adds logic to allow a drag down gesture to enter desktop mode from a
fullscreen task. This prevents status bar from opening notification
shade if the input is on handle. Also enables interacting with caption
menu buttons.

Bug: 247551213
Test: Drag handle in fullscreen task to enter desktop mode. Confirm
handle/caption menu buttons work through status bar. Confirm no
other caption functionality is affected.

Change-Id: I19bb9d750a9305939e4672429d9cce153290a014
parent a40e956a
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2022 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 android:shape="rectangle"
       xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@android:color/white" />
    <corners android:radius="20dp" />
</shape>
+1 −1
Original line number Diff line number Diff line
@@ -20,7 +20,7 @@ android:id="@+id/handle_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:background="@drawable/decor_caption_title">
android:background="@drawable/decor_caption_menu_background">
    <Button
        style="@style/CaptionButtonStyle"
        android:id="@+id/fullscreen_button"
+84 −9
Original line number Diff line number Diff line
@@ -44,6 +44,8 @@ import android.view.View;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;

import androidx.annotation.Nullable;

import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -71,6 +73,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
    private DesktopModeController mDesktopModeController;
    private EventReceiver mEventReceiver;
    private InputMonitor mInputMonitor;
    private boolean mTransitionDragActive;

    private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
    private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
@@ -91,6 +94,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
        mDisplayController = displayController;
        mSyncQueue = syncQueue;
        mDesktopModeController = desktopModeController;
        mTransitionDragActive = false;
    }

    @Override
@@ -280,7 +284,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
                    mDragResizeCallback.onDragResizeEnd(
                            e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                    if (e.getRawY(dragPointerIdx) <= statusBarHeight
                            && windowingMode == WINDOWING_MODE_FREEFORM) {
                            && DesktopModeStatus.isActive(mContext)) {
                        mDesktopModeController.setDesktopModeActive(false);
                    }
                    break;
@@ -298,24 +302,95 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
        @Override
        public void onInputEvent(InputEvent event) {
            boolean handled = false;
            if (event instanceof MotionEvent
                    && ((MotionEvent) event).getActionMasked() == MotionEvent.ACTION_UP) {
            if (event instanceof MotionEvent) {
                handled = true;
                CaptionWindowDecorViewModel.this.handleMotionEvent((MotionEvent) event);
                CaptionWindowDecorViewModel.this.handleReceivedMotionEvent((MotionEvent) event);
            }
            finishInputEvent(event, handled);
        }
    }

    // If any input received is outside of caption bounds, turn off handle menu
    private void handleMotionEvent(MotionEvent ev) {
    /**
     * Handle MotionEvents relevant to focused task's caption that don't directly touch it
     * @param ev the {@link MotionEvent} received by {@link EventReceiver}
     */
    private void handleReceivedMotionEvent(MotionEvent ev) {
        if (!DesktopModeStatus.isActive(mContext)) {
            handleCaptionThroughStatusBar(ev);
        }
        handleEventOutsideFocusedCaption(ev);
        // Prevent status bar from reacting to a caption drag.
        if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
            mInputMonitor.pilferPointers();
        }
    }

    // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
    private void handleEventOutsideFocusedCaption(MotionEvent ev) {
        int action = ev.getActionMasked();
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            CaptionWindowDecoration focusedDecor = getFocusedDecor();
            if (focusedDecor == null) {
                return;
            }

            if (!mTransitionDragActive) {
                focusedDecor.closeHandleMenuIfNeeded(ev);
            }
        }
    }

    /**
     * Perform caption actions if not able to through normal means.
     * Turn on desktop mode if handle is dragged below status bar.
     */
    private void handleCaptionThroughStatusBar(MotionEvent ev) {
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: {
                // Begin drag through status bar if applicable.
                CaptionWindowDecoration focusedDecor = getFocusedDecor();
                if (focusedDecor != null && !DesktopModeStatus.isActive(mContext)
                        && focusedDecor.checkTouchEventInHandle(ev)) {
                    mTransitionDragActive = true;
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                CaptionWindowDecoration focusedDecor = getFocusedDecor();
                if (focusedDecor == null) {
                    mTransitionDragActive = false;
                    return;
                }
                if (mTransitionDragActive) {
                    mTransitionDragActive = false;
                    int statusBarHeight = mDisplayController
                            .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
                    if (ev.getY() > statusBarHeight) {
                        mDesktopModeController.setDesktopModeActive(true);
                        return;
                    }
                }
                focusedDecor.checkClickEvent(ev);
                break;
            }
            case MotionEvent.ACTION_CANCEL: {
                mTransitionDragActive = false;
            }
        }
    }

    @Nullable
    private CaptionWindowDecoration getFocusedDecor() {
        int size = mWindowDecorByTaskId.size();
        CaptionWindowDecoration focusedDecor = null;
        for (int i = 0; i < size; i++) {
            CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
            if (decoration != null) {
                decoration.closeHandleMenuIfNeeded(ev);
            CaptionWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
            if (decor != null && decor.isFocused()) {
                focusedDecor = decor;
                break;
            }
        }
        return focusedDecor;
    }


+78 −16
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
@@ -234,7 +235,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
     * Sets the visibility of buttons and color of caption based on desktop mode status
     *
     */
    public void setButtonVisibility() {
    void setButtonVisibility() {
        mDesktopActive = DesktopModeStatus.isActive(mContext);
        int v = mDesktopActive ? View.VISIBLE : View.GONE;
        View caption = mResult.mRootView.findViewById(R.id.caption);
@@ -253,7 +254,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
        caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
    }

    public boolean isHandleMenuActive() {
    boolean isHandleMenuActive() {
        return mHandleMenu != null;
    }

@@ -268,7 +269,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
    /**
     * Create and display handle menu window
     */
    public void createHandleMenu() {
    void createHandleMenu() {
        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
        final Resources resources = mDecorWindowContext.getResources();
        int x = mRelayoutParams.mCaptionX;
@@ -289,7 +290,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
    /**
     * Close the handle menu window
     */
    public void closeHandleMenu() {
    void closeHandleMenu() {
        if (!isHandleMenuActive()) return;
        mHandleMenu.releaseView();
        mHandleMenu = null;
@@ -304,22 +305,83 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
    /**
     * Close an open handle menu if input is outside of menu coordinates
     * @param ev the tapped point to compare against
     * @return
     */
    public void closeHandleMenuIfNeeded(MotionEvent ev) {
        if (mHandleMenu != null) {
    void closeHandleMenuIfNeeded(MotionEvent ev) {
        if (isHandleMenuActive()) {
            if (!checkEventInCaptionView(ev, R.id.caption)) {
                closeHandleMenu();
            }
        }
    }

    boolean isFocused() {
        return mTaskInfo.isFocused;
    }

    /**
     * Offset the coordinates of a {@link MotionEvent} to be in the same coordinate space as caption
     * @param ev the {@link MotionEvent} to offset
     * @return the point of the input in local space
     */
    private PointF offsetCaptionLocation(MotionEvent ev) {
        PointF result = new PointF(ev.getX(), ev.getY());
        Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
                .positionInParent;
            final Resources resources = mDecorWindowContext.getResources();
            ev.offsetLocation(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
            ev.offsetLocation(-positionInParent.x, -positionInParent.y);
            int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
            int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
            if (!(ev.getX() >= 0 && ev.getY()  >= 0
                    && ev.getX()  <= width && ev.getY()  <= height)) {
                closeHandleMenu();
        result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
        result.offset(-positionInParent.x, -positionInParent.y);
        return result;
    }

    /**
     * Determine if a passed MotionEvent is in a view in caption
     * @param ev the {@link MotionEvent} to check
     * @param layoutId the id of the view
     * @return {@code true} if event is inside the specified view, {@code false} if not
     */
    private boolean checkEventInCaptionView(MotionEvent ev, int layoutId) {
        if (mResult.mRootView == null) return false;
        PointF inputPoint = offsetCaptionLocation(ev);
        View view = mResult.mRootView.findViewById(layoutId);
        return view != null && view.pointInView(inputPoint.x, inputPoint.y, 0);
    }

    boolean checkTouchEventInHandle(MotionEvent ev) {
        if (isHandleMenuActive()) return false;
        return checkEventInCaptionView(ev, R.id.caption_handle);
    }

    /**
     * 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
     * (i.e. the button was clicked through status bar layer)
     * @param ev the MotionEvent to compare
     */
    void checkClickEvent(MotionEvent ev) {
        if (mResult.mRootView == null) return;
        View caption = mResult.mRootView.findViewById(R.id.caption);
        PointF inputPoint = offsetCaptionLocation(ev);
        if (!isHandleMenuActive()) {
            View handle = caption.findViewById(R.id.caption_handle);
            clickIfPointInView(inputPoint, handle);
        } else {
            View menu = mHandleMenu.mWindowViewHost.getView();
            View fullscreen = menu.findViewById(R.id.fullscreen_button);
            if (clickIfPointInView(inputPoint, fullscreen)) return;
            View desktop = menu.findViewById(R.id.desktop_button);
            if (clickIfPointInView(inputPoint, desktop)) return;
            View split = menu.findViewById(R.id.split_screen_button);
            if (clickIfPointInView(inputPoint, split)) return;
            View more = menu.findViewById(R.id.more_button);
            clickIfPointInView(inputPoint, more);
        }
    }

    private boolean clickIfPointInView(PointF inputPoint, View v) {
        if (v.pointInView(inputPoint.x - v.getLeft(), inputPoint.y, 0)) {
            mOnCaptionButtonClickListener.onClick(v);
            return true;
        }
        return false;
    }

    @Override