diff options
author | 2025-02-14 23:27:28 -0800 | |
---|---|---|
committer | 2025-02-14 23:27:28 -0800 | |
commit | 124c717693336df722328dce1c74aa13e1a47e0a (patch) | |
tree | afcb4d027856a5daed37b874264e6d0ce4a582ed | |
parent | 439f8966d7d3a47c49f877794f8d170e4c66519f (diff) | |
parent | f6c88eef8c1e92ad8429175be5b8f3725da7addc (diff) |
Merge "Update drag resize input listener in bg thread" into main
5 files changed, 522 insertions, 99 deletions
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 dd5439a8aa10..7871179a50de 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 @@ -339,6 +339,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel, FocusT taskInfo, taskSurface, mMainHandler, + mMainExecutor, mBgExecutor, mMainChoreographer, mSyncQueue, 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 23bb2aa616f9..49510c8060fc 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 @@ -48,6 +48,8 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.WindowInsets; import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.window.DesktopModeFlags; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; @@ -58,6 +60,7 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; @@ -69,6 +72,7 @@ import com.android.wm.shell.windowdecor.extension.TaskInfoKt; */ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> { private final Handler mHandler; + private final @ShellMainThread ShellExecutor mMainExecutor; private final @ShellBackgroundThread ShellExecutor mBgExecutor; private final Choreographer mChoreographer; private final SyncTransactionQueue mSyncQueue; @@ -90,6 +94,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL RunningTaskInfo taskInfo, SurfaceControl taskSurface, Handler handler, + @ShellMainThread ShellExecutor mainExecutor, @ShellBackgroundThread ShellExecutor bgExecutor, Choreographer choreographer, SyncTransactionQueue syncQueue, @@ -97,6 +102,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorViewHostSupplier); mHandler = handler; + mMainExecutor = mainExecutor; mBgExecutor = bgExecutor; mChoreographer = choreographer; mSyncQueue = syncQueue; @@ -287,8 +293,14 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) { closeDragResizeListener(); + final ShellExecutor bgExecutor = + DesktopModeFlags.ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD.isTrue() + ? mBgExecutor : mMainExecutor; mDragResizeListener = new DragResizeInputListener( mContext, + WindowManagerGlobal.getWindowSession(), + mMainExecutor, + bgExecutor, mTaskInfo, mHandler, mChoreographer, @@ -299,17 +311,19 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mSurfaceControlTransactionSupplier, mDisplayController); } - + final DragResizeInputListener newListener = mDragResizeListener; final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()) .getScaledTouchSlop(); - final Resources res = mResult.mRootView.getResources(); - mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */, - new Size(mResult.mWidth, mResult.mHeight), - getResizeEdgeHandleSize(res), - getResizeHandleEdgeInset(res), getFineResizeCornerSize(res), - getLargeResizeCornerSize(res), DragResizeWindowGeometry.DisabledEdge.NONE), - touchSlop); + final DragResizeWindowGeometry newGeometry = new DragResizeWindowGeometry( + 0 /* taskCornerRadius */, + new Size(mResult.mWidth, mResult.mHeight), + getResizeEdgeHandleSize(res), + getResizeHandleEdgeInset(res), getFineResizeCornerSize(res), + getLargeResizeCornerSize(res), DragResizeWindowGeometry.DisabledEdge.NONE); + newListener.addInitializedCallback(() -> { + mDragResizeListener.setGeometry(newGeometry, touchSlop); + }); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 271dead467b4..dca376f7df0e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -68,6 +68,7 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.WindowInsets; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import android.widget.ImageButton; import android.window.DesktopModeFlags; import android.window.TaskSnapshot; @@ -479,7 +480,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (shouldDelayUpdate) { return; } - updateDragResizeListener(mDecorationContainerSurface, inFullImmersive); + updateDragResizeListenerIfNeeded(mDecorationContainerSurface, inFullImmersive); } @@ -587,7 +588,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin closeMaximizeMenu(); notifyNoCaptionHandle(); } - updateDragResizeListener(oldDecorationSurface, inFullImmersive); + updateDragResizeListenerIfNeeded(oldDecorationSurface, inFullImmersive); updateMaximizeMenu(startT, inFullImmersive); Trace.endSection(); // DesktopModeWindowDecoration#relayout } @@ -665,22 +666,42 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return mUserContext.getUser(); } - private void updateDragResizeListener(SurfaceControl oldDecorationSurface, + private void updateDragResizeListenerIfNeeded(@Nullable SurfaceControl containerSurface, boolean inFullImmersive) { + final boolean taskPositionChanged = !mTaskInfo.positionInParent.equals(mPositionInParent); if (!isDragResizable(mTaskInfo, inFullImmersive)) { - if (!mTaskInfo.positionInParent.equals(mPositionInParent)) { + if (taskPositionChanged) { // We still want to track caption bar's exclusion region on a non-resizeable task. updateExclusionRegion(inFullImmersive); } closeDragResizeListener(); return; } + updateDragResizeListener(containerSurface, + (geometryChanged) -> { + if (geometryChanged || taskPositionChanged) { + updateExclusionRegion(inFullImmersive); + } + }); + } - if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) { + private void updateDragResizeListener(@Nullable SurfaceControl containerSurface, + Consumer<Boolean> onUpdateFinished) { + final boolean containerSurfaceChanged = containerSurface != mDecorationContainerSurface; + final boolean isFirstDragResizeListener = mDragResizeListener == null; + final boolean shouldCreateListener = containerSurfaceChanged || isFirstDragResizeListener; + if (containerSurfaceChanged) { closeDragResizeListener(); - Trace.beginSection("DesktopModeWindowDecoration#relayout-DragResizeInputListener"); + } + if (shouldCreateListener) { + final ShellExecutor bgExecutor = + DesktopModeFlags.ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD.isTrue() + ? mBgExecutor : mMainExecutor; mDragResizeListener = new DragResizeInputListener( mContext, + WindowManagerGlobal.getWindowSession(), + mMainExecutor, + bgExecutor, mTaskInfo, mHandler, mChoreographer, @@ -691,24 +712,20 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mSurfaceControlTransactionSupplier, mDisplayController, mDesktopModeEventLogger); - Trace.endSection(); } - + final DragResizeInputListener newListener = mDragResizeListener; final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()) .getScaledTouchSlop(); - - // If either task geometry or position have changed, update this task's - // exclusion region listener final Resources res = mResult.mRootView.getResources(); - if (mDragResizeListener.setGeometry( - new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius, - new Size(mResult.mWidth, mResult.mHeight), - getResizeEdgeHandleSize(res), getResizeHandleEdgeInset(res), - getFineResizeCornerSize(res), getLargeResizeCornerSize(res), - mDisabledResizingEdge), touchSlop) - || !mTaskInfo.positionInParent.equals(mPositionInParent)) { - updateExclusionRegion(inFullImmersive); - } + final DragResizeWindowGeometry newGeometry = new DragResizeWindowGeometry( + mRelayoutParams.mCornerRadius, + new Size(mResult.mWidth, mResult.mHeight), + getResizeEdgeHandleSize(res), getResizeHandleEdgeInset(res), + getFineResizeCornerSize(res), getLargeResizeCornerSize(res), + mDisabledResizingEdge); + newListener.addInitializedCallback(() -> { + onUpdateFinished.accept(newListener.setGeometry(newGeometry, touchSlop)); + }); } private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo, @@ -1711,7 +1728,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ private Region getGlobalExclusionRegion(boolean inFullImmersive) { Region exclusionRegion; - if (mDragResizeListener != null && isDragResizable(mTaskInfo, inFullImmersive)) { + if (mDragResizeListener != null + && isDragResizable(mTaskInfo, inFullImmersive)) { exclusionRegion = mDragResizeListener.getCornersRegion(); } else { exclusionRegion = new Region(); 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 index b531079f18c1..7a4a834e9dc2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -43,6 +43,7 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.os.Trace; import android.util.Size; import android.view.Choreographer; import android.view.IWindowSession; @@ -54,14 +55,19 @@ import android.view.PointerIcon; import android.view.SurfaceControl; import android.view.View; import android.view.ViewConfiguration; -import android.view.WindowManagerGlobal; import android.window.InputTransferToken; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.desktopmode.DesktopModeEventLogger; +import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; +import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; @@ -73,28 +79,45 @@ import java.util.function.Supplier; */ class DragResizeInputListener implements AutoCloseable { private static final String TAG = "DragResizeInputListener"; - private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession(); + private final IWindowSession mWindowSession; + private final TaskResizeInputEventReceiverFactory mEventReceiverFactory; + private final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier; private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier; private final int mDisplayId; - private final IBinder mClientToken; + @VisibleForTesting + final IBinder mClientToken; private final SurfaceControl mDecorationSurface; - private final InputChannel mInputChannel; - private final TaskResizeInputEventReceiver mInputEventReceiver; + private InputChannel mInputChannel; + private TaskResizeInputEventReceiver mInputEventReceiver; private final Context mContext; + private final @ShellBackgroundThread ShellExecutor mBgExecutor; private final RunningTaskInfo mTaskInfo; - private final SurfaceControl mInputSinkSurface; - private final IBinder mSinkClientToken; - private final InputChannel mSinkInputChannel; + private final Handler mHandler; + private final Choreographer mChoreographer; + private SurfaceControl mInputSinkSurface; + @VisibleForTesting + final IBinder mSinkClientToken; + private InputChannel mSinkInputChannel; private final DisplayController mDisplayController; + /** TODO: b/396490344 - this desktop-specific class should be abstracted out of here. */ private final DesktopModeEventLogger mDesktopModeEventLogger; + private final DragPositioningCallback mDragPositioningCallback; private final Region mTouchRegion = new Region(); + private final List<Runnable> mOnInitializedCallbacks = new ArrayList<>(); + + private final Runnable mInitInputChannels; + private boolean mClosed = false; DragResizeInputListener( Context context, + IWindowSession windowSession, + @ShellMainThread ShellExecutor mainExecutor, + @ShellBackgroundThread ShellExecutor bgExecutor, + TaskResizeInputEventReceiverFactory eventReceiverFactory, RunningTaskInfo taskInfo, Handler handler, Choreographer choreographer, @@ -106,20 +129,138 @@ class DragResizeInputListener implements AutoCloseable { DisplayController displayController, DesktopModeEventLogger desktopModeEventLogger) { mContext = context; + mWindowSession = windowSession; + mBgExecutor = bgExecutor; + mEventReceiverFactory = eventReceiverFactory; mTaskInfo = taskInfo; - mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier; + mHandler = handler; + mChoreographer = choreographer; mDisplayId = displayId; mDecorationSurface = decorationSurface; + mDragPositioningCallback = callback; + mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier; + mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier; mDisplayController = displayController; mDesktopModeEventLogger = desktopModeEventLogger; mClientToken = new Binder(); + mSinkClientToken = new Binder(); + + // Setting up input channels for both the resize listener and the input sink requires + // multiple blocking binder calls, so it's moved to a bg thread to keep the shell.main + // thread free. + // The input event receiver must be created back in the shell.main thread though because + // its geometry and util methods are updated/queried from the shell.main thread. + mInitInputChannels = () -> { + final InputSetUpResult result = setUpInputChannels(mDisplayId, mWindowSession, + mDecorationSurface, mClientToken, mSinkClientToken, + mSurfaceControlBuilderSupplier, + mSurfaceControlTransactionSupplier); + mainExecutor.execute(() -> { + if (mClosed) { + return; + } + mInputSinkSurface = result.mInputSinkSurface; + mInputChannel = result.mInputChannel; + mSinkInputChannel = result.mSinkInputChannel; + Trace.beginSection("DragResizeInputListener#ctor-initReceiver"); + mInputEventReceiver = mEventReceiverFactory.create( + mContext, + mTaskInfo, + mInputChannel, + mDragPositioningCallback, + mHandler, + mChoreographer, + () -> { + final DisplayLayout layout = + mDisplayController.getDisplayLayout(mDisplayId); + return new Size(layout.width(), layout.height()); + }, + this::updateSinkInputChannel, + mDesktopModeEventLogger); + mInputEventReceiver.setTouchSlop( + ViewConfiguration.get(mContext).getScaledTouchSlop()); + for (Runnable initCallback : mOnInitializedCallbacks) { + initCallback.run(); + } + mOnInitializedCallbacks.clear(); + Trace.endSection(); + }); + }; + bgExecutor.execute(mInitInputChannels); + } + + DragResizeInputListener( + Context context, + IWindowSession windowSession, + @ShellMainThread ShellExecutor mainExecutor, + @ShellBackgroundThread ShellExecutor bgExecutor, + RunningTaskInfo taskInfo, + Handler handler, + Choreographer choreographer, + int displayId, + SurfaceControl decorationSurface, + DragPositioningCallback callback, + Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, + Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, + DisplayController displayController, + DesktopModeEventLogger desktopModeEventLogger) { + this(context, windowSession, mainExecutor, bgExecutor, + new DefaultTaskResizeInputEventReceiverFactory(), taskInfo, + handler, choreographer, displayId, decorationSurface, callback, + surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, + displayController, desktopModeEventLogger); + } + + DragResizeInputListener( + Context context, + IWindowSession windowSession, + @ShellMainThread ShellExecutor mainExecutor, + @ShellBackgroundThread ShellExecutor bgExecutor, + RunningTaskInfo taskInfo, + Handler handler, + Choreographer choreographer, + int displayId, + SurfaceControl decorationSurface, + DragPositioningCallback callback, + Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, + Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, + DisplayController displayController) { + this(context, windowSession, mainExecutor, bgExecutor, taskInfo, + handler, choreographer, displayId, decorationSurface, callback, + surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, + displayController, new DesktopModeEventLogger()); + } + + /** + * Registers a callback to be invoked when the input listener has finished initializing. If + * already finished, the callback will be invoked immediately. + */ + void addInitializedCallback(Runnable onReady) { + if (mInputEventReceiver != null) { + onReady.run(); + return; + } + mOnInitializedCallbacks.add(onReady); + } + + @ShellBackgroundThread + private static InputSetUpResult setUpInputChannels( + int displayId, + @NonNull IWindowSession windowSession, + @NonNull SurfaceControl decorationSurface, + @NonNull IBinder clientToken, + @NonNull IBinder sinkClientToken, + @NonNull Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, + @NonNull Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) { + Trace.beginSection("DragResizeInputListener#setUpInputChannels"); final InputTransferToken inputTransferToken = new InputTransferToken(); - mInputChannel = new InputChannel(); + final InputChannel inputChannel = new InputChannel(); + final InputChannel sinkInputChannel = new InputChannel(); try { - mWindowSession.grantInputChannel( - mDisplayId, - mDecorationSurface, - mClientToken, + windowSession.grantInputChannel( + displayId, + decorationSurface, + clientToken, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, @@ -127,37 +268,27 @@ class DragResizeInputListener implements AutoCloseable { TYPE_APPLICATION, null /* windowToken */, inputTransferToken, - TAG + " of " + decorationSurface.toString(), - mInputChannel); + TAG + " of " + decorationSurface, + inputChannel); } catch (RemoteException e) { e.rethrowFromSystemServer(); } - mInputEventReceiver = new TaskResizeInputEventReceiver(context, mTaskInfo, mInputChannel, - callback, - handler, choreographer, () -> { - final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId); - return new Size(layout.width(), layout.height()); - }, this::updateSinkInputChannel, mDesktopModeEventLogger); - mInputEventReceiver.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop()); - - mInputSinkSurface = surfaceControlBuilderSupplier.get() + final SurfaceControl inputSinkSurface = surfaceControlBuilderSupplier.get() .setName("TaskInputSink of " + decorationSurface) .setContainerLayer() - .setParent(mDecorationSurface) - .setCallsite("DragResizeInputListener.constructor") + .setParent(decorationSurface) + .setCallsite("DragResizeInputListener.setUpInputChannels") .build(); - mSurfaceControlTransactionSupplier.get() - .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER) - .show(mInputSinkSurface) + surfaceControlTransactionSupplier.get() + .setLayer(inputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER) + .show(inputSinkSurface) .apply(); - mSinkClientToken = new Binder(); - mSinkInputChannel = new InputChannel(); try { - mWindowSession.grantInputChannel( - mDisplayId, - mInputSinkSurface, - mSinkClientToken, + windowSession.grantInputChannel( + displayId, + inputSinkSurface, + sinkClientToken, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, 0 /* privateFlags */, @@ -166,26 +297,12 @@ class DragResizeInputListener implements AutoCloseable { null /* windowToken */, inputTransferToken, "TaskInputSink of " + decorationSurface, - mSinkInputChannel); + sinkInputChannel); } catch (RemoteException e) { e.rethrowFromSystemServer(); } - } - - DragResizeInputListener( - Context context, - RunningTaskInfo taskInfo, - Handler handler, - Choreographer choreographer, - int displayId, - SurfaceControl decorationSurface, - DragPositioningCallback callback, - Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, - Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, - DisplayController displayController) { - this(context, taskInfo, handler, choreographer, displayId, decorationSurface, callback, - surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, displayController, - new DesktopModeEventLogger()); + Trace.endSection(); + return new InputSetUpResult(inputSinkSurface, inputChannel, sinkInputChannel); } /** @@ -268,35 +385,101 @@ class DragResizeInputListener implements AutoCloseable { } boolean shouldHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) { - return mInputEventReceiver.shouldHandleEvent(e, offset); + return mInputEventReceiver != null && mInputEventReceiver.shouldHandleEvent(e, offset); } boolean isHandlingDragResize() { - return mInputEventReceiver.isHandlingEvents(); + return mInputEventReceiver != null && mInputEventReceiver.isHandlingEvents(); } @Override public void close() { - mInputEventReceiver.dispose(); - mInputChannel.dispose(); - try { - mWindowSession.remove(mClientToken); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + mClosed = true; + if (mInitInputChannels != null) { + mBgExecutor.removeCallbacks(mInitInputChannels); + } + if (mInputEventReceiver != null) { + mInputEventReceiver.dispose(); + } + if (mInputChannel != null) { + mInputChannel.dispose(); + } + if (mSinkInputChannel != null) { + mSinkInputChannel.dispose(); } - mSinkInputChannel.dispose(); - try { - mWindowSession.remove(mSinkClientToken); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + if (mInputSinkSurface != null) { + mSurfaceControlTransactionSupplier.get() + .remove(mInputSinkSurface) + .apply(); } - mSurfaceControlTransactionSupplier.get() - .remove(mInputSinkSurface) - .apply(); + + mBgExecutor.execute(() -> { + try { + mWindowSession.remove(mClientToken); + mWindowSession.remove(mSinkClientToken); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + }); } - private static class TaskResizeInputEventReceiver extends InputEventReceiver implements + private static class InputSetUpResult { + final @NonNull SurfaceControl mInputSinkSurface; + final @NonNull InputChannel mInputChannel; + final @NonNull InputChannel mSinkInputChannel; + + InputSetUpResult(@NonNull SurfaceControl inputSinkSurface, + @NonNull InputChannel inputChannel, + @NonNull InputChannel sinkInputChannel) { + mInputSinkSurface = inputSinkSurface; + mInputChannel = inputChannel; + mSinkInputChannel = sinkInputChannel; + } + } + + /** A factory that creates {@link TaskResizeInputEventReceiver}s. */ + interface TaskResizeInputEventReceiverFactory { + @NonNull + TaskResizeInputEventReceiver create( + @NonNull Context context, + @NonNull RunningTaskInfo taskInfo, + @NonNull InputChannel inputChannel, + @NonNull DragPositioningCallback callback, + @NonNull Handler handler, + @NonNull Choreographer choreographer, + @NonNull Supplier<Size> displayLayoutSizeSupplier, + @NonNull Consumer<Region> touchRegionConsumer, + @NonNull DesktopModeEventLogger desktopModeEventLogger + ); + } + + /** A default implementation of {@link TaskResizeInputEventReceiverFactory}. */ + static class DefaultTaskResizeInputEventReceiverFactory + implements TaskResizeInputEventReceiverFactory { + @Override + @NonNull + public TaskResizeInputEventReceiver create( + @NonNull Context context, + @NonNull RunningTaskInfo taskInfo, + @NonNull InputChannel inputChannel, + @NonNull DragPositioningCallback callback, + @NonNull Handler handler, + @NonNull Choreographer choreographer, + @NonNull Supplier<Size> displayLayoutSizeSupplier, + @NonNull Consumer<Region> touchRegionConsumer, + @NonNull DesktopModeEventLogger desktopModeEventLogger) { + return new TaskResizeInputEventReceiver(context, taskInfo, inputChannel, callback, + handler, choreographer, displayLayoutSizeSupplier, touchRegionConsumer, + desktopModeEventLogger); + } + } + + /** + * An input event receiver to handle motion events on the task's corners and edges for + * drag-resizing, as well as keeping the input sink updated. + */ + static class TaskResizeInputEventReceiver extends InputEventReceiver implements DragDetector.MotionEventHandler { @NonNull private final Context mContext; @NonNull private final RunningTaskInfo mTaskInfo; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt new file mode 100644 index 000000000000..7341e098add5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2025 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.app.ActivityManager +import android.content.Context +import android.graphics.Region +import android.os.Handler +import android.os.Looper +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.util.Size +import android.view.Choreographer +import android.view.Display +import android.view.IWindowSession +import android.view.InputChannel +import android.view.SurfaceControl +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestHandler +import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.android.wm.shell.TestShellExecutor +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.desktopmode.DesktopModeEventLogger +import com.android.wm.shell.util.StubTransaction +import com.android.wm.shell.windowdecor.DragResizeInputListener.TaskResizeInputEventReceiver +import com.google.common.truth.Truth.assertThat +import java.util.function.Consumer +import java.util.function.Supplier +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify + +/** + * Tests for [DragResizeInputListener]. + * + * Build/Install/Run: + * atest WMShellUnitTests:DragResizeInputListenerTest + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) +class DragResizeInputListenerTest : ShellTestCase() { + private val testMainExecutor = TestShellExecutor() + private val testBgExecutor = TestShellExecutor() + private val mockWindowSession = mock<IWindowSession>() + private val mockInputEventReceiver = mock<TaskResizeInputEventReceiver>() + + @Test + fun testGrantInputChannelOffMainThread() { + create() + testMainExecutor.flushAll() + + verifyNoInputChannelGrantRequests() + } + + @Test + fun testInitializationCallback_waitsForBgSetup() { + val inputListener = create() + + val callback = TestInitializationCallback() + inputListener.addInitializedCallback(callback) + assertThat(callback.initialized).isFalse() + + testBgExecutor.flushAll() + testMainExecutor.flushAll() + + assertThat(callback.initialized).isTrue() + } + + @Test + fun testInitializationCallback_alreadyInitialized_callsBackImmediately() { + val inputListener = create() + testBgExecutor.flushAll() + testMainExecutor.flushAll() + + val callback = TestInitializationCallback() + inputListener.addInitializedCallback(callback) + + assertThat(callback.initialized).isTrue() + } + + @Test + fun testClose_beforeBgSetup_cancelsBgSetup() { + val inputListener = create() + + inputListener.close() + testBgExecutor.flushAll() + + verifyNoInputChannelGrantRequests() + } + + @Test + fun testClose_beforeBgSetupResultSet_cancelsInit() { + val inputListener = create() + val callback = TestInitializationCallback() + inputListener.addInitializedCallback(callback) + + testBgExecutor.flushAll() + inputListener.close() + testMainExecutor.flushAll() + + assertThat(callback.initialized).isFalse() + } + + @Test + fun testClose_afterInit_disposesOfReceiver() { + val inputListener = create() + + testBgExecutor.flushAll() + testMainExecutor.flushAll() + inputListener.close() + + verify(mockInputEventReceiver).dispose() + } + + @Test + fun testClose_afterInit_removesTokens() { + val inputListener = create() + + inputListener.close() + testBgExecutor.flushAll() + + verify(mockWindowSession).remove(inputListener.mClientToken) + verify(mockWindowSession).remove(inputListener.mSinkClientToken) + } + + private fun verifyNoInputChannelGrantRequests() { + verify(mockWindowSession, never()) + .grantInputChannel( + anyInt(), + any(), + any(), + anyOrNull(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyOrNull(), + any(), + any(), + any(), + ) + } + + private fun create(): DragResizeInputListener = + DragResizeInputListener( + context, + mockWindowSession, + testMainExecutor, + testBgExecutor, + TestTaskResizeInputEventReceiverFactory(mockInputEventReceiver), + TestRunningTaskInfoBuilder().build(), + TestHandler(Looper.getMainLooper()), + mock<Choreographer>(), + Display.DEFAULT_DISPLAY, + mock<SurfaceControl>(), + mock<DragPositioningCallback>(), + { SurfaceControl.Builder() }, + { StubTransaction() }, + mock<DisplayController>(), + mock<DesktopModeEventLogger>(), + ) + + private class TestInitializationCallback : Runnable { + var initialized: Boolean = false + private set + + override fun run() { + initialized = true + } + } + + private class TestTaskResizeInputEventReceiverFactory( + private val mockInputEventReceiver: TaskResizeInputEventReceiver + ) : DragResizeInputListener.TaskResizeInputEventReceiverFactory { + override fun create( + context: Context, + taskInfo: ActivityManager.RunningTaskInfo, + inputChannel: InputChannel, + callback: DragPositioningCallback, + handler: Handler, + choreographer: Choreographer, + displayLayoutSizeSupplier: Supplier<Size?>, + touchRegionConsumer: Consumer<Region?>, + desktopModeEventLogger: DesktopModeEventLogger, + ): TaskResizeInputEventReceiver = mockInputEventReceiver + } +} |