summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> 2025-02-14 23:27:28 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-02-14 23:27:28 -0800
commit124c717693336df722328dce1c74aa13e1a47e0a (patch)
treeafcb4d027856a5daed37b874264e6d0ce4a582ed
parent439f8966d7d3a47c49f877794f8d170e4c66519f (diff)
parentf6c88eef8c1e92ad8429175be5b8f3725da7addc (diff)
Merge "Update drag resize input listener in bg thread" into main
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java323
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeInputListenerTest.kt207
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
+ }
+}