diff options
8 files changed, 534 insertions, 10 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index a4d1ce18f32b..f59e045958b5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -137,11 +137,13 @@ public class WMShellModule { @Provides static WindowDecorViewModel<?> provideWindowDecorViewModel( Context context, + @ShellMainThread Handler mainHandler, ShellTaskOrganizer taskOrganizer, DisplayController displayController, SyncTransactionQueue syncQueue) { return new CaptionWindowDecorViewModel( context, + mainHandler, taskOrganizer, displayController, syncQueue); 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 d678f031698e..6d28d73996f0 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 @@ -24,6 +24,7 @@ import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.content.Context; +import android.os.Handler; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; @@ -43,15 +44,18 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption private final ActivityTaskManager mActivityTaskManager; private final ShellTaskOrganizer mTaskOrganizer; private final Context mContext; + private final Handler mMainHandler; private final DisplayController mDisplayController; private final SyncTransactionQueue mSyncQueue; public CaptionWindowDecorViewModel( Context context, + Handler mainHandler, ShellTaskOrganizer taskOrganizer, DisplayController displayController, SyncTransactionQueue syncQueue) { mContext = context; + mMainHandler = mainHandler; mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); mTaskOrganizer = taskOrganizer; mDisplayController = displayController; @@ -67,9 +71,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption mTaskOrganizer, taskInfo, taskSurface, + mMainHandler, mSyncQueue); - CaptionTouchEventListener touchEventListener = new CaptionTouchEventListener(taskInfo); + TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration); + CaptionTouchEventListener touchEventListener = + new CaptionTouchEventListener(taskInfo, taskPositioner); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); + windowDecoration.setDragResizeCallback(taskPositioner); onTaskInfoChanged(taskInfo, windowDecoration); return windowDecoration; } @@ -87,10 +95,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption private final int mTaskId; private final WindowContainerToken mTaskToken; + private final DragResizeCallback mDragResizeCallback; - private CaptionTouchEventListener(RunningTaskInfo taskInfo) { + private int mDragPointerId = -1; + + private CaptionTouchEventListener( + RunningTaskInfo taskInfo, DragResizeCallback dragResizeCallback) { mTaskId = taskInfo.taskId; mTaskToken = taskInfo.token; + mDragResizeCallback = dragResizeCallback; } @Override @@ -120,6 +133,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption if (v.getId() != R.id.caption) { return false; } + handleEventForMove(e); + if (e.getAction() != MotionEvent.ACTION_DOWN) { return false; } @@ -132,5 +147,28 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption mSyncQueue.queue(wct); return true; } + + private void handleEventForMove(MotionEvent e) { + switch (e.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mDragPointerId = e.getPointerId(0); + mDragResizeCallback.onDragResizeStart( + 0 /* ctrlType */, e.getRawX(0), e.getRawY(0)); + break; + case MotionEvent.ACTION_MOVE: { + int dragPointerIdx = e.findPointerIndex(mDragPointerId); + mDragResizeCallback.onDragResizeMove( + e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); + break; + } + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: { + int dragPointerIdx = e.findPointerIndex(mDragPointerId); + mDragResizeCallback.onDragResizeEnd( + e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); + break; + } + } + } } } 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 569be23b9f52..572516d2f8c9 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 @@ -17,12 +17,14 @@ package com.android.wm.shell.windowdecor; import android.app.ActivityManager; +import android.app.WindowConfiguration; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.VectorDrawable; +import android.os.Handler; import android.view.SurfaceControl; import android.view.View; import android.window.WindowContainerTransaction; @@ -48,13 +50,22 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL // The thickness of shadows of a window that doesn't have focus in DIP. private static final int DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP = 5; + // Height of button (32dp) + 2 * margin (5dp each) private static final int DECOR_CAPTION_HEIGHT_IN_DIP = 42; + private static final int RESIZE_HANDLE_IN_DIP = 30; + private static final Rect EMPTY_OUTSET = new Rect(); + private static final Rect RESIZE_HANDLE_OUTSET = new Rect( + RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP); + private final Handler mHandler; private final SyncTransactionQueue mSyncQueue; private View.OnClickListener mOnCaptionButtonClickListener; private View.OnTouchListener mOnCaptionTouchListener; + private DragResizeCallback mDragResizeCallback; + + private DragResizeInputListener mDragResizeListener; private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult = new WindowDecoration.RelayoutResult<>(); @@ -65,9 +76,11 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, + Handler handler, SyncTransactionQueue syncQueue) { super(context, displayController, taskOrganizer, taskInfo, taskSurface); + mHandler = handler; mSyncQueue = syncQueue; } @@ -78,15 +91,24 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mOnCaptionTouchListener = onCaptionTouchListener; } + void setDragResizeCallback(DragResizeCallback dragResizeCallback) { + mDragResizeCallback = dragResizeCallback; + } + void relayout(ActivityManager.RunningTaskInfo taskInfo) { final int shadowRadiusDp = taskInfo.isFocused ? DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP : DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP; + final boolean isFreeform = mTaskInfo.configuration.windowConfiguration.getWindowingMode() + == WindowConfiguration.WINDOWING_MODE_FREEFORM; + final boolean isDragResizeable = isFreeform && mTaskInfo.isResizeable; + final Rect outset = isDragResizeable ? RESIZE_HANDLE_OUTSET : EMPTY_OUTSET; WindowDecorLinearLayout oldRootView = mResult.mRootView; + final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); final WindowContainerTransaction wct = new WindowContainerTransaction(); relayout(taskInfo, R.layout.caption_window_decoration, oldRootView, - DECOR_CAPTION_HEIGHT_IN_DIP, EMPTY_OUTSET, shadowRadiusDp, t, wct, mResult); + DECOR_CAPTION_HEIGHT_IN_DIP, outset, shadowRadiusDp, t, wct, mResult); taskInfo = null; // Clear it just in case we use it accidentally mSyncQueue.runInSync(transaction -> { @@ -97,11 +119,31 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL }); if (mResult.mRootView == null) { + // This means the task is hidden. Nothing is set up in this case including the + // decoration surface. return; } if (oldRootView != mResult.mRootView) { setupRootView(); } + + if (!isDragResizeable) { + closeDragResizeListener(); + return; + } + + if (oldDecorationSurface != mDecorationContainerSurface) { + closeDragResizeListener(); + mDragResizeListener = new DragResizeInputListener( + mContext, + mHandler, + mDisplay.getDisplayId(), + mDecorationContainerSurface, + mDragResizeCallback); + } + + mDragResizeListener.setGeometry( + mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP)); } /** @@ -140,4 +182,18 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL VectorDrawable closeBackground = (VectorDrawable) close.getBackground(); closeBackground.setTintList(buttonTintColor); } + + private void closeDragResizeListener() { + if (mDragResizeListener == null) { + return; + } + mDragResizeListener.close(); + mDragResizeListener = null; + } + + @Override + public void close() { + closeDragResizeListener(); + super.close(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java new file mode 100644 index 000000000000..ee160a15df19 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java @@ -0,0 +1,46 @@ +/* + * 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. + */ + +package com.android.wm.shell.windowdecor; + +/** + * Callback called when receiving drag-resize or drag-move related input events. + */ +public interface DragResizeCallback { + /** + * Called when a drag resize starts. + * + * @param ctrlType {@link TaskPositioner.CtrlType} indicating the direction of resizing, use + * {@code 0} to indicate it's a move + * @param x x coordinate in window decoration coordinate system where the drag resize starts + * @param y y coordinate in window decoration coordinate system where the drag resize starts + */ + void onDragResizeStart(@TaskPositioner.CtrlType int ctrlType, float x, float y); + + /** + * Called when the pointer moves during a drag resize. + * @param x x coordinate in window decoration coordinate system of the new pointer location + * @param y y coordinate in window decoration coordinate system of the new pointer location + */ + void onDragResizeMove(float x, float y); + + /** + * Called when a drag resize stops. + * @param x x coordinate in window decoration coordinate system where the drag resize stops + * @param y y coordinate in window decoration coordinate system where the drag resize stops + */ + void onDragResizeEnd(float x, float y); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java new file mode 100644 index 000000000000..c6bbb027c8e6 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -0,0 +1,288 @@ +/* + * 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. + */ + +package com.android.wm.shell.windowdecor; + +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; + +import android.content.Context; +import android.graphics.Rect; +import android.graphics.Region; +import android.hardware.input.InputManager; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.view.IWindowSession; +import android.view.InputChannel; +import android.view.InputEvent; +import android.view.InputEventReceiver; +import android.view.MotionEvent; +import android.view.PointerIcon; +import android.view.SurfaceControl; +import android.view.WindowManagerGlobal; + +import com.android.internal.view.BaseIWindow; + +/** + * An input event listener registered to InputDispatcher to receive input events on task edges and + * convert them to drag resize requests. + */ +class DragResizeInputListener implements AutoCloseable { + private static final String TAG = "DragResizeInputListener"; + + private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession(); + private final Handler mHandler; + private final InputManager mInputManager; + + private final int mDisplayId; + private final BaseIWindow mFakeWindow; + private final IBinder mFocusGrantToken; + private final SurfaceControl mDecorationSurface; + private final InputChannel mInputChannel; + private final TaskResizeInputEventReceiver mInputEventReceiver; + private final com.android.wm.shell.windowdecor.DragResizeCallback mCallback; + + private int mWidth; + private int mHeight; + private int mResizeHandleThickness; + + private int mDragPointerId = -1; + + DragResizeInputListener( + Context context, + Handler handler, + int displayId, + SurfaceControl decorationSurface, + DragResizeCallback callback) { + mInputManager = context.getSystemService(InputManager.class); + mHandler = handler; + mDisplayId = displayId; + mDecorationSurface = decorationSurface; + // Use a fake window as the backing surface is a container layer and we don't want to create + // a buffer layer for it so we can't use ViewRootImpl. + mFakeWindow = new BaseIWindow(); + mFakeWindow.setSession(mWindowSession); + mFocusGrantToken = new Binder(); + mInputChannel = new InputChannel(); + try { + mWindowSession.grantInputChannel( + mDisplayId, + new SurfaceControl(mDecorationSurface, TAG), + mFakeWindow, + null /* hostInputToken */, + FLAG_NOT_FOCUSABLE, + PRIVATE_FLAG_TRUSTED_OVERLAY, + TYPE_APPLICATION, + mFocusGrantToken, + TAG + " of " + decorationSurface.toString(), + mInputChannel); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + + mInputEventReceiver = new TaskResizeInputEventReceiver(mInputChannel, mHandler.getLooper()); + mCallback = callback; + } + + /** + * Updates geometry of this drag resize handler. Needs to be called every time there is a size + * change to notify the input event receiver it's ready to take the next input event. Otherwise + * it'll keep batching move events and the drag resize process is stalled. + * + * This is also used to update the touch regions of this handler every event dispatched here is + * a potential resize request. + * + * @param width The width of the drag resize handler in pixels, including resize handle + * thickness. That is task width + 2 * resize handle thickness. + * @param height The height of the drag resize handler in pixels, including resize handle + * thickness. That is task height + 2 * resize handle thickness. + * @param resizeHandleThickness The thickness of the resize handle in pixels. + */ + void setGeometry(int width, int height, int resizeHandleThickness) { + if (mWidth == width && mHeight == height + && mResizeHandleThickness == resizeHandleThickness) { + return; + } + + mWidth = width; + mHeight = height; + mResizeHandleThickness = resizeHandleThickness; + + Region touchRegion = new Region(); + final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness); + touchRegion.union(topInputBounds); + + final Rect leftInputBounds = new Rect(0, mResizeHandleThickness, + mResizeHandleThickness, mHeight - mResizeHandleThickness); + touchRegion.union(leftInputBounds); + + final Rect rightInputBounds = new Rect( + mWidth - mResizeHandleThickness, mResizeHandleThickness, + mWidth, mHeight - mResizeHandleThickness); + touchRegion.union(rightInputBounds); + + final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness, + mWidth, mHeight); + touchRegion.union(bottomInputBounds); + + try { + mWindowSession.updateInputChannel( + mInputChannel.getToken(), + mDisplayId, + new SurfaceControl( + mDecorationSurface, "DragResizeInputListener#setTouchRegion"), + FLAG_NOT_FOCUSABLE, + PRIVATE_FLAG_TRUSTED_OVERLAY, + touchRegion); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + + // This marks all relevant components have handled the previous resize event and can take + // the next one now. + mInputEventReceiver.onHandledLastResizeEvent(); + } + + @Override + public void close() { + mInputChannel.dispose(); + try { + mWindowSession.remove(mFakeWindow); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + private class TaskResizeInputEventReceiver extends InputEventReceiver { + private boolean mWaitingForLastResizeEventHandled; + + private TaskResizeInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + + private void onHandledLastResizeEvent() { + mWaitingForLastResizeEventHandled = false; + consumeBatchedInputEvents(-1); + } + + @Override + public void onBatchedInputEventPending(int source) { + // InputEventReceiver keeps continuous move events in a batched event until explicitly + // consuming it or an incompatible event shows up (likely an up event in this case). We + // continue to keep move events in the next batched event until we receive a geometry + // update so that we don't put too much pressure on the framework with excessive number + // of input events if it can't handle them fast enough. It's more responsive to always + // resize the task to the latest received coordinates. + if (!mWaitingForLastResizeEventHandled) { + consumeBatchedInputEvents(-1); + } + } + + @Override + public void onInputEvent(InputEvent inputEvent) { + finishInputEvent(inputEvent, handleInputEvent(inputEvent)); + } + + private boolean handleInputEvent(InputEvent inputEvent) { + if (!(inputEvent instanceof MotionEvent)) { + return false; + } + + MotionEvent e = (MotionEvent) inputEvent; + switch (e.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + mDragPointerId = e.getPointerId(0); + mCallback.onDragResizeStart( + calculateCtrlType(e.getX(0), e.getY(0)), e.getRawX(0), e.getRawY(0)); + mWaitingForLastResizeEventHandled = false; + break; + } + case MotionEvent.ACTION_MOVE: { + int dragPointerIndex = e.findPointerIndex(mDragPointerId); + mCallback.onDragResizeMove( + e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex)); + mWaitingForLastResizeEventHandled = true; + break; + } + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: { + int dragPointerIndex = e.findPointerIndex(mDragPointerId); + mCallback.onDragResizeEnd( + e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex)); + mWaitingForLastResizeEventHandled = false; + mDragPointerId = -1; + break; + } + case MotionEvent.ACTION_HOVER_ENTER: + case MotionEvent.ACTION_HOVER_MOVE: { + updateCursorType(e.getXCursorPosition(), e.getYCursorPosition()); + break; + } + case MotionEvent.ACTION_HOVER_EXIT: + mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT); + break; + } + return true; + } + + @TaskPositioner.CtrlType + private int calculateCtrlType(float x, float y) { + int ctrlType = 0; + if (x < mResizeHandleThickness) { + ctrlType |= TaskPositioner.CTRL_TYPE_LEFT; + } + if (x > mWidth - mResizeHandleThickness) { + ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT; + } + if (y < mResizeHandleThickness) { + ctrlType |= TaskPositioner.CTRL_TYPE_TOP; + } + if (y > mHeight - mResizeHandleThickness) { + ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM; + } + return ctrlType; + } + + private void updateCursorType(float x, float y) { + @TaskPositioner.CtrlType int ctrlType = calculateCtrlType(x, y); + + int cursorType = PointerIcon.TYPE_DEFAULT; + switch (ctrlType) { + case TaskPositioner.CTRL_TYPE_LEFT: + case TaskPositioner.CTRL_TYPE_RIGHT: + cursorType = PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; + break; + case TaskPositioner.CTRL_TYPE_TOP: + case TaskPositioner.CTRL_TYPE_BOTTOM: + cursorType = PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; + break; + case TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_TOP: + case TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_BOTTOM: + cursorType = PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW; + break; + case TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_BOTTOM: + case TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_TOP: + cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW; + break; + } + mInputManager.setPointerIconType(cursorType); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java new file mode 100644 index 000000000000..280569b05d87 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java @@ -0,0 +1,99 @@ +/* + * 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. + */ + +package com.android.wm.shell.windowdecor; + +import android.annotation.IntDef; +import android.graphics.PointF; +import android.graphics.Rect; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.ShellTaskOrganizer; + +class TaskPositioner implements DragResizeCallback { + + @IntDef({CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM}) + @interface CtrlType {} + + static final int CTRL_TYPE_LEFT = 1; + static final int CTRL_TYPE_RIGHT = 2; + static final int CTRL_TYPE_TOP = 4; + static final int CTRL_TYPE_BOTTOM = 8; + + private final ShellTaskOrganizer mTaskOrganizer; + private final WindowDecoration mWindowDecoration; + + private final Rect mTaskBoundsAtDragStart = new Rect(); + private final PointF mResizeStartPoint = new PointF(); + private final Rect mResizeTaskBounds = new Rect(); + + private int mCtrlType; + + TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) { + mTaskOrganizer = taskOrganizer; + mWindowDecoration = windowDecoration; + } + + @Override + public void onDragResizeStart(int ctrlType, float x, float y) { + mCtrlType = ctrlType; + + mTaskBoundsAtDragStart.set( + mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds()); + mResizeStartPoint.set(x, y); + } + + @Override + public void onDragResizeMove(float x, float y) { + changeBounds(x, y); + } + + @Override + public void onDragResizeEnd(float x, float y) { + changeBounds(x, y); + + mCtrlType = 0; + mTaskBoundsAtDragStart.setEmpty(); + mResizeStartPoint.set(0, 0); + } + + private void changeBounds(float x, float y) { + float deltaX = x - mResizeStartPoint.x; + mResizeTaskBounds.set(mTaskBoundsAtDragStart); + if ((mCtrlType & CTRL_TYPE_LEFT) != 0) { + mResizeTaskBounds.left += deltaX; + } + if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) { + mResizeTaskBounds.right += deltaX; + } + float deltaY = y - mResizeStartPoint.y; + if ((mCtrlType & CTRL_TYPE_TOP) != 0) { + mResizeTaskBounds.top += deltaY; + } + if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) { + mResizeTaskBounds.bottom += deltaY; + } + if (mCtrlType == 0) { + mResizeTaskBounds.offset((int) deltaX, (int) deltaY); + } + + if (!mResizeTaskBounds.isEmpty()) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds); + mTaskOrganizer.applyTransaction(wct); + } + } +} diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 974340ee2510..fb1450d0aa61 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4880,9 +4880,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return true; } - // TODO(b/165794880): Freeform task organizer doesn't support drag-resize yet. Remove - // the special case when it does. - if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FREEFORM) { + if (task.isOrganized()) { return true; } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 7e06b8823260..8389dbde3e14 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -288,10 +288,7 @@ final class InputMonitor { SurfaceControl touchableRegionCrop = null; final Task task = w.getTask(); if (task != null) { - // TODO(b/165794636): Remove the special case for freeform window once drag resizing is - // handled by WM shell. - if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN - && !task.inFreeformWindowingMode()) { + if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { // If the window is in a TaskManaged by a TaskOrganizer then most cropping will // be applied using the SurfaceControl hierarchy from the Organizer. This means // we need to make sure that these changes in crop are reflected in the input |