Loading libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml 0 → 100644 +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> libs/WindowManager/Shell/res/layout/caption_handle_menu.xml +1 −1 Original line number Diff line number Diff line Loading @@ -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" Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +84 −9 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading @@ -91,6 +94,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mDisplayController = displayController; mSyncQueue = syncQueue; mDesktopModeController = desktopModeController; mTransitionDragActive = false; } @Override Loading Loading @@ -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; Loading @@ -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; } Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +78 −16 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -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; } Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading Loading
libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml 0 → 100644 +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>
libs/WindowManager/Shell/res/layout/caption_handle_menu.xml +1 −1 Original line number Diff line number Diff line Loading @@ -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" Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +84 −9 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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(); Loading @@ -91,6 +94,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mDisplayController = displayController; mSyncQueue = syncQueue; mDesktopModeController = desktopModeController; mTransitionDragActive = false; } @Override Loading Loading @@ -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; Loading @@ -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; } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +78 −16 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -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; } Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading