From 88f9f15233ef1c1debcdea6124496ab32b3968f6 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 22 Nov 2021 14:05:17 -0800 Subject: [PATCH] Implement gesture monitors using spy windows Bug: 162194035 Test: manual, presubmit Change-Id: I9673b49d50dc163150ffb5bb10a378e66a7d3bfb --- .../server/input/GestureMonitorSpyWindow.java | 92 ++++++++++++ .../server/input/InputManagerService.java | 141 +++++++++++++++--- .../server/wm/InputManagerCallback.java | 20 +++ 3 files changed, 232 insertions(+), 21 deletions(-) create mode 100644 services/core/java/com/android/server/input/GestureMonitorSpyWindow.java diff --git a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java new file mode 100644 index 0000000000000..9d6764eb9cdf1 --- /dev/null +++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java @@ -0,0 +1,92 @@ +/* + * 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.server.input; + +import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; + +import android.view.InputApplicationHandle; +import android.view.InputChannel; +import android.view.InputMonitor; +import android.view.InputWindowHandle; +import android.view.SurfaceControl; +import android.view.WindowManager; + +/** + * An internal implementation of an {@link InputMonitor} that uses a spy window. + * + * This spy window is a layer container in the SurfaceFlinger hierarchy that does not have any + * graphical buffer, but can still receive input. It is parented to the DisplayContent so + * that it can spy on any pointer events that start in the DisplayContent bounds. When the + * object is constructed, it will add itself to SurfaceFlinger. + */ +class GestureMonitorSpyWindow { + final InputApplicationHandle mApplicationHandle; + final InputWindowHandle mWindowHandle; + + // The InputChannel and SurfaceControl are owned by this object. + final InputChannel mClientChannel; + final SurfaceControl mInputSurface; + + GestureMonitorSpyWindow(String name, int displayId, int pid, int uid, SurfaceControl sc, + InputChannel inputChannel) { + mClientChannel = inputChannel; + mInputSurface = sc; + + mApplicationHandle = new InputApplicationHandle(null, name, + DEFAULT_DISPATCHING_TIMEOUT_MILLIS); + mWindowHandle = new InputWindowHandle(mApplicationHandle, displayId); + + mWindowHandle.name = name; + mWindowHandle.token = mClientChannel.getToken(); + mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; + mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + mWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS; + mWindowHandle.visible = true; + mWindowHandle.focusable = false; + mWindowHandle.hasWallpaper = false; + mWindowHandle.paused = false; + mWindowHandle.ownerPid = pid; + mWindowHandle.ownerUid = uid; + mWindowHandle.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY; + mWindowHandle.scaleFactor = 1.0f; + mWindowHandle.trustedOverlay = true; + mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */); + + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.setInputWindowInfo(mInputSurface, mWindowHandle); + t.setLayer(mInputSurface, Integer.MAX_VALUE); + t.setPosition(mInputSurface, 0, 0); + t.setCrop(mInputSurface, null /* crop to parent surface */); + t.show(mInputSurface); + + t.apply(); + } + + void remove() { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.hide(mInputSurface); + t.remove(mInputSurface); + t.apply(); + + mClientChannel.dispose(); + } + + String dump() { + return "name='" + mWindowHandle.name + "', inputChannelToken=" + + mClientChannel.getToken() + " displayId=" + mWindowHandle.displayId; + } +} diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 783a88ca29bf7..98c4e9db04e69 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -150,6 +150,8 @@ public class InputManagerService extends IInputManager.Stub static final String TAG = "InputManager"; static final boolean DEBUG = false; + private static final boolean USE_SPY_WINDOW_GESTURE_MONITORS = false; + private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml"; private static final String PORT_ASSOCIATIONS_PATH = "etc/input-port-associations.xml"; @@ -277,6 +279,12 @@ public class InputManagerService extends IInputManager.Stub @GuardedBy("mPointerDisplayIdLock") private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY; + + // Holds all the registered gesture monitors that are implemented as spy windows. The spy + // windows are mapped by their InputChannel tokens. + @GuardedBy("mInputMonitors") + final Map mInputMonitors = new HashMap<>(); + private static native long nativeInit(InputManagerService service, Context context, MessageQueue messageQueue); private static native void nativeStart(long ptr); @@ -716,33 +724,68 @@ public class InputManagerService extends IInputManager.Stub inputChannelName, Binder.getCallingPid()); } + @NonNull + private InputChannel createSpyWindowGestureMonitor(String name, int displayId, int pid, + int uid) { + final SurfaceControl sc = mWindowManagerCallbacks.createSurfaceForGestureMonitor(name, + displayId); + if (sc == null) { + throw new IllegalArgumentException( + "Could not create gesture monitor surface on display: " + displayId); + } + final InputChannel channel = createInputChannel(name); + + synchronized (mInputMonitors) { + mInputMonitors.put(channel.getToken(), + new GestureMonitorSpyWindow(name, displayId, pid, uid, sc, channel)); + } + + final InputChannel outInputChannel = new InputChannel(); + channel.copyTo(outInputChannel); + return outInputChannel; + } + + private void removeSpyWindowGestureMonitor(IBinder inputChannelToken) { + final GestureMonitorSpyWindow monitor; + synchronized (mInputMonitors) { + monitor = mInputMonitors.remove(inputChannelToken); + } + removeInputChannel(inputChannelToken); + if (monitor == null) return; + monitor.remove(); + } + /** * Creates an input monitor that will receive pointer events for the purposes of system-wide * gesture interpretation. * - * @param inputChannelName The input channel name. + * @param requestedName The input channel name. * @param displayId Target display id. * @return The input channel. */ @Override // Binder call - public InputMonitor monitorGestureInput(String inputChannelName, int displayId) { + public InputMonitor monitorGestureInput(@NonNull String requestedName, int displayId) { if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT, - "monitorInputRegion()")) { + "monitorGestureInput()")) { throw new SecurityException("Requires MONITOR_INPUT permission"); } - Objects.requireNonNull(inputChannelName, "inputChannelName must not be null."); + Objects.requireNonNull(requestedName, "name must not be null."); if (displayId < Display.DEFAULT_DISPLAY) { throw new IllegalArgumentException("displayId must >= 0."); } + final String name = "[Gesture Monitor] " + requestedName; final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); final long ident = Binder.clearCallingIdentity(); try { - InputChannel inputChannel = nativeCreateInputMonitor( - mPtr, displayId, true /*isGestureMonitor*/, inputChannelName, pid); - InputMonitorHost host = new InputMonitorHost(inputChannel.getToken()); - return new InputMonitor(inputChannel, host); + final InputChannel inputChannel = + USE_SPY_WINDOW_GESTURE_MONITORS + ? createSpyWindowGestureMonitor(name, displayId, pid, uid) + : nativeCreateInputMonitor(mPtr, displayId, true /*isGestureMonitor*/, + requestedName, pid); + return new InputMonitor(inputChannel, new InputMonitorHost(inputChannel.getToken())); } finally { Binder.restoreCallingIdentity(ident); } @@ -2563,37 +2606,51 @@ public class InputManagerService extends IInputManager.Stub String dumpStr = nativeDump(mPtr); if (dumpStr != null) { pw.println(dumpStr); - dumpAssociations(pw); } + + pw.println("Input Manager Service (Java) State:"); + dumpAssociations(pw, " " /*prefix*/); + dumpSpyWindowGestureMonitors(pw, " " /*prefix*/); } - private void dumpAssociations(PrintWriter pw) { + private void dumpAssociations(PrintWriter pw, String prefix) { if (!mStaticAssociations.isEmpty()) { - pw.println("Static Associations:"); + pw.println(prefix + "Static Associations:"); mStaticAssociations.forEach((k, v) -> { - pw.print(" port: " + k); + pw.print(prefix + " port: " + k); pw.println(" display: " + v); }); } synchronized (mAssociationsLock) { if (!mRuntimeAssociations.isEmpty()) { - pw.println("Runtime Associations:"); + pw.println(prefix + "Runtime Associations:"); mRuntimeAssociations.forEach((k, v) -> { - pw.print(" port: " + k); + pw.print(prefix + " port: " + k); pw.println(" display: " + v); }); } if (!mUniqueIdAssociations.isEmpty()) { - pw.println("Unique Id Associations:"); + pw.println(prefix + "Unique Id Associations:"); mUniqueIdAssociations.forEach((k, v) -> { - pw.print(" port: " + k); + pw.print(prefix + " port: " + k); pw.println(" uniqueId: " + v); }); } } } + private void dumpSpyWindowGestureMonitors(PrintWriter pw, String prefix) { + synchronized (mInputMonitors) { + if (mInputMonitors.isEmpty()) return; + pw.println(prefix + "Gesture Monitors (implemented as spy windows):"); + int i = 0; + for (final GestureMonitorSpyWindow monitor : mInputMonitors.values()) { + pw.append(prefix + " " + i++ + ": ").println(monitor.dump()); + } + } + } + private boolean checkCallingPermission(String permission, String func) { // Quick check: if the calling permission is me, it's all okay. if (Binder.getCallingPid() == Process.myPid()) { @@ -2618,6 +2675,7 @@ public class InputManagerService extends IInputManager.Stub synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */} synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ } synchronized (mPointerDisplayIdLock) { /* Test if blocked by pointer display id lock */ } + synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ } nativeMonitor(mPtr); } @@ -2686,6 +2744,11 @@ public class InputManagerService extends IInputManager.Stub // Native callback. private void notifyInputChannelBroken(IBinder token) { + synchronized (mInputMonitors) { + if (mInputMonitors.containsKey(token)) { + removeSpyWindowGestureMonitor(token); + } + } mWindowManagerCallbacks.notifyInputChannelBroken(token); } @@ -2721,6 +2784,17 @@ public class InputManagerService extends IInputManager.Stub // Native callback private void notifyWindowUnresponsive(IBinder token, String reason) { + int gestureMonitorPid = -1; + synchronized (mInputMonitors) { + final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token); + if (gestureMonitor != null) { + gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid; + } + } + if (gestureMonitorPid != -1) { + mWindowManagerCallbacks.notifyGestureMonitorUnresponsive(gestureMonitorPid, reason); + return; + } mWindowManagerCallbacks.notifyWindowUnresponsive(token, reason); } @@ -2731,6 +2805,17 @@ public class InputManagerService extends IInputManager.Stub // Native callback private void notifyWindowResponsive(IBinder token) { + int gestureMonitorPid = -1; + synchronized (mInputMonitors) { + final GestureMonitorSpyWindow gestureMonitor = mInputMonitors.get(token); + if (gestureMonitor != null) { + gestureMonitorPid = gestureMonitor.mWindowHandle.ownerPid; + } + } + if (gestureMonitorPid != -1) { + mWindowManagerCallbacks.notifyGestureMonitorResponsive(gestureMonitorPid); + return; + } mWindowManagerCallbacks.notifyWindowResponsive(token); } @@ -3184,6 +3269,16 @@ public class InputManagerService extends IInputManager.Stub * pointers such as the mouse cursor and touch spots for the given display. */ SurfaceControl getParentSurfaceForPointers(int displayId); + + /** + * Create a {@link SurfaceControl} that can be configured to receive input over the entire + * display to implement a gesture monitor. The surface will not have a graphical buffer. + * @param name the name of the gesture monitor + * @param displayId the display to create the window in + * @return the SurfaceControl of the new layer container surface + */ + @Nullable + SurfaceControl createSurfaceForGestureMonitor(String name, int displayId); } /** @@ -3258,20 +3353,24 @@ public class InputManagerService extends IInputManager.Stub * Interface for the system to handle request from InputMonitors. */ private final class InputMonitorHost extends IInputMonitorHost.Stub { - private final IBinder mToken; + private final IBinder mInputChannelToken; - InputMonitorHost(IBinder token) { - mToken = token; + InputMonitorHost(IBinder inputChannelToken) { + mInputChannelToken = inputChannelToken; } @Override public void pilferPointers() { - nativePilferPointers(mPtr, mToken); + nativePilferPointers(mPtr, mInputChannelToken); } @Override public void dispose() { - nativeRemoveInputChannel(mPtr, mToken); + if (USE_SPY_WINDOW_GESTURE_MONITORS) { + removeSpyWindowGestureMonitor(mInputChannelToken); + return; + } + nativeRemoveInputChannel(mPtr, mInputChannelToken); } } diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index f91969b2c5587..1f0fdcf0a8d2c 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -24,6 +24,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS; import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.PointF; import android.os.Debug; import android.os.IBinder; @@ -254,6 +255,25 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } } + @Override + @Nullable + public SurfaceControl createSurfaceForGestureMonitor(String name, int displayId) { + synchronized (mService.mGlobalLock) { + final DisplayContent dc = mService.mRoot.getDisplayContent(displayId); + if (dc == null) { + Slog.e(TAG, "Failed to create a gesture monitor on display: " + displayId + + " - DisplayContent not found."); + return null; + } + return mService.makeSurfaceBuilder(dc.getSession()) + .setContainerLayer() + .setName(name) + .setCallsite("createSurfaceForGestureMonitor") + .setParent(dc.getSurfaceControl()) + .build(); + } + } + /** Waits until the built-in input devices have been configured. */ public boolean waitForInputDevicesReady(long timeoutMillis) { synchronized (mInputDevicesReadyMonitor) {