diff options
4 files changed, 184 insertions, 26 deletions
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml b/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml new file mode 100644 index 000000000000..416287d2cbb3 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml @@ -0,0 +1,21 @@ +<?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> diff --git a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml index d9a140b810f8..582a11cfdb8e 100644 --- a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml +++ b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml @@ -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" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index ca15f0002fac..6acb6b49082b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -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; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 03cad043ed67..d5ade46d2208 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -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,24 +305,85 @@ 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) { - 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)) { + 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; + 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 public void close() { closeDragResizeListener(); |