Implement gesture monitors using spy windows
Bug: 162194035 Test: manual, presubmit Change-Id: I9673b49d50dc163150ffb5bb10a378e66a7d3bfb
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user