Implement gesture monitors using spy windows

Bug: 162194035
Test: manual, presubmit
Change-Id: I9673b49d50dc163150ffb5bb10a378e66a7d3bfb
This commit is contained in:
Prabir Pradhan
2021-11-22 14:05:17 -08:00
parent dc1c61a2bb
commit 88f9f15233
3 changed files with 232 additions and 21 deletions

View File

@@ -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;
}
}

View File

@@ -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<IBinder, GestureMonitorSpyWindow> 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);
}
}

View File

@@ -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) {