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

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

Merge "Enable interacting with caption through status bar." into tm-qpr-dev

parents 2345ce43 2625245f
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
@@ -288,7 +292,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;
@@ -306,24 +310,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;
@@ -243,7 +244,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);
@@ -262,7 +263,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;
    }

@@ -277,7 +278,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;
@@ -298,7 +299,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
    /**
     * Close the handle menu window
     */
    public void closeHandleMenu() {
    void closeHandleMenu() {
        if (!isHandleMenuActive()) return;
        mHandleMenu.releaseView();
        mHandleMenu = null;
@@ -313,22 +314,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