Android Input Spy Window

Android Input Spy Window Analysis

本文整理 AOSP 14 中 spy window 的概念、调用链、分发原理、pilferPointers() 抢占逻辑,以及它和 InputMonitor 的关系。重点结论:

  • spy window 是一种 InputWindow 配置,核心标志是 InputConfig.SPY
  • InputManager.monitorGestureInput() 返回的 android.view.InputMonitor 在 AOSP 14 中底层由 GestureMonitorSpyWindow 实现,因此它实际创建的是一个 spy window。
  • InputManagerService.monitorInput() / native createInputMonitor() 是另一套 global monitor 机制,不属于窗口体系,没有 Z-order 和 touchable region 概念。

1. Spy Window 的定义

方法: android.os.InputConfig

文件: frameworks/native/libs/input/android/os/InputConfig.aidl

aidl 复制代码
/**
 * An input spy window. This window will receive all pointer events within its touchable
 * area, but will not stop events from being sent to other windows below it in z-order.
 * An input event will be dispatched to all spy windows above the top non-spy window at the
 * event's coordinates.
 */
SPY = 1 << 14,

含义:

  • spy window 能收到自己 touchable area 内的 pointer event。
  • 它不会拦截或阻止 Z-order 下方窗口接收事件。
  • 只有位于"命中的最上层非 spy window"之上的 spy windows 会收到事件。

例如 Z-order 从上到下是:

text 复制代码
spy1
spy2
appWindow
spy3

如果触摸点命中 appWindow,则分发目标是:

text 复制代码
appWindow + spy1 + spy2

spy3 不会收到,因为它在真正命中的非 spy window 下面。

2. Java API 到 Spy Window 的创建链路

2.1 方法: InputManager.monitorGestureInput(String name, int displayId)

文件: frameworks/base/core/java/android/hardware/input/InputManager.java

java 复制代码
/**
 * Monitor input on the specified display for gestures.
 *
 * @hide
 */
public InputMonitor monitorGestureInput(String name, int displayId) {
    return mGlobal.monitorGestureInput(name, displayId);
}

这是 SystemUI、Shell 等系统组件通常调用的入口。

2.2 方法: InputManagerGlobal.monitorGestureInput(String name, int displayId)

文件: frameworks/base/core/java/android/hardware/input/InputManagerGlobal.java

java 复制代码
/**
 * @see InputManager#monitorGestureInput(String, int)
 */
public InputMonitor monitorGestureInput(String name, int displayId) {
    try {
        return mIm.monitorGestureInput(new Binder(), name, displayId);
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
}

这里通过 Binder 调用 IInputManager.monitorGestureInput(...),进入 system_server 的 InputManagerService

2.3 方法: InputManagerService.monitorGestureInput(...)

文件: frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

java 复制代码
@Override // Binder call
public InputMonitor monitorGestureInput(IBinder monitorToken, @NonNull String requestedName,
        int displayId) {
    if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT,
            "monitorGestureInput()")) {
        throw new SecurityException("Requires MONITOR_INPUT permission");
    }

    final SurfaceControl sc = mWindowManagerCallbacks.createSurfaceForGestureMonitor(name,
            displayId);

    final InputChannel inputChannel = createSpyWindowGestureMonitor(
            monitorToken, name, sc, displayId, pid, uid);
    return new InputMonitor(inputChannel,
        new InputMonitorHost(inputChannel.getToken()),
        new SurfaceControl(sc, "IMS.monitorGestureInput"));
}

关键点:

  • 调用者必须有 MONITOR_INPUT 权限。
  • WMS 创建一个用于 gesture monitor 的 SurfaceControl
  • IMS 调用 createSpyWindowGestureMonitor(...) 创建 spy window。
  • 返回给调用方的是 android.view.InputMonitor

2.4 方法: InputManagerService.createSpyWindowGestureMonitor(...)

文件: frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

java 复制代码
@NonNull
private InputChannel createSpyWindowGestureMonitor(IBinder monitorToken, String name,
        SurfaceControl sc, int displayId, int pid, int uid) {
    final InputChannel channel = createInputChannel(name);

    monitorToken.linkToDeath(() -> removeSpyWindowGestureMonitor(channel.getToken()), 0);

    synchronized (mInputMonitors) {
        mInputMonitors.put(channel.getToken(),
                new GestureMonitorSpyWindow(monitorToken, name, displayId, pid, uid, sc,
                        channel));
    }

    final InputChannel outInputChannel = new InputChannel();
    channel.copyTo(outInputChannel);
    return outInputChannel;
}

关键点:

  • 创建一对 input channel。
  • GestureMonitorSpyWindow 包装 channel 和 surface。
  • 以 channel token 为 key 保存到 mInputMonitors
  • 返回 client 侧 InputChannel 给调用者。

2.5 方法: GestureMonitorSpyWindow.GestureMonitorSpyWindow(...)

文件: frameworks/base/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java

java 复制代码
GestureMonitorSpyWindow(IBinder token, String name, int displayId, int pid, int uid,
        SurfaceControl sc, InputChannel inputChannel) {
    mWindowHandle = new InputWindowHandle(mApplicationHandle, displayId);

    mWindowHandle.name = name;
    mWindowHandle.token = mClientChannel.getToken();
    mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
    mWindowHandle.ownerPid = pid;
    mWindowHandle.ownerUid = uid;
    mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
    mWindowHandle.inputConfig =
            InputConfig.NOT_FOCUSABLE | InputConfig.SPY | InputConfig.TRUSTED_OVERLAY;

    final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
    t.setInputWindowInfo(mInputSurface, mWindowHandle);
    t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_GESTURE_MONITOR);
    t.show(mInputSurface);
    t.apply();
}

这是 monitorGestureInput() 和 spy window 关联的直接证据:

text 复制代码
monitorGestureInput()
  -> createSpyWindowGestureMonitor()
  -> new GestureMonitorSpyWindow()
  -> InputConfig.SPY

GestureMonitorSpyWindow 是没有图形 buffer 的输入 surface,但它通过 setInputWindowInfo(...) 进入输入窗口列表。

3. 直接使用 INPUT_FEATURE_SPY 的窗口路径

除了 monitorGestureInput() 自动创建 spy window,系统窗口也可以直接设置 WindowManager.LayoutParams.INPUT_FEATURE_SPY

3.1 方法/字段: WindowManager.LayoutParams.INPUT_FEATURE_SPY

文件: frameworks/base/core/java/android/view/WindowManager.java

java 复制代码
/**
 * An input spy window. This window will receive all pointer events within its touchable
 * area, but will not stop events from being sent to other windows below it in z-order.
 * An input event will be dispatched to all spy windows above the top non-spy window at the
 * event's coordinates.
 *
 * @hide
 */
@RequiresPermission(permission.MONITOR_INPUT)
public static final int INPUT_FEATURE_SPY = 1 << 2;

3.2 方法: WindowManagerService.sanitizeSpyWindow(...)

文件: frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

java 复制代码
/**
 * You need MONITOR_INPUT permission to be able to set INPUT_FEATURE_SPY.
 */
private int sanitizeSpyWindow(int inputFeatures, String windowName, int callingUid,
        int callingPid) {
    if ((inputFeatures & INPUT_FEATURE_SPY) == 0) {
        return inputFeatures;
    }
    final int permissionResult = mContext.checkPermission(
            permission.MONITOR_INPUT, callingPid, callingUid);
    if (permissionResult != PackageManager.PERMISSION_GRANTED) {
        throw new IllegalArgumentException("Cannot use INPUT_FEATURE_SPY from '" + windowName
                + "' because it doesn't the have MONITOR_INPUT permission");
    }
    return inputFeatures;
}

直接设置 INPUT_FEATURE_SPY 也必须有 MONITOR_INPUT 权限。

3.3 方法/静态映射: InputConfigAdapter.INPUT_FEATURE_TO_CONFIG_MAP

文件: frameworks/base/services/core/java/com/android/server/wm/InputConfigAdapter.java

java 复制代码
private static final List<FlagMapping> INPUT_FEATURE_TO_CONFIG_MAP = List.of(
        new FlagMapping(
                LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL,
                InputConfig.NO_INPUT_CHANNEL, false /* inverted */),
        new FlagMapping(
                LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY,
                InputConfig.DISABLE_USER_ACTIVITY, false /* inverted */),
        new FlagMapping(
                LayoutParams.INPUT_FEATURE_SPY,
                InputConfig.SPY, false /* inverted */));

这说明 Java 层 LayoutParams.INPUT_FEATURE_SPY 最终会转换成 native/input 层可见的 InputConfig.SPY

3.4 示例: UdfpsControllerOverlay.coreLayoutParams

文件: frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt

kotlin 复制代码
private val coreLayoutParams = WindowManager.LayoutParams(
    WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
    0 /* flags set in computeLayoutParams() */,
    PixelFormat.TRANSLUCENT
).apply {
    privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY

    if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
        inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
    }
}

这是直接把已有系统 overlay 窗口配置为 spy window 的例子。适合屏下指纹这类"overlay 自身需要旁听触摸,但不应吞掉普通窗口事件"的场景。

4. Native 侧的安全约束

方法: InputDispatcher::setInputWindowsLocked(...)

文件: frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

cpp 复制代码
// Ensure all spy windows are trusted overlays
LOG_ALWAYS_FATAL_IF(info.isSpy() &&
                            !info.inputConfig.test(
                                    WindowInfo::InputConfig::TRUSTED_OVERLAY),
                    "%s has feature SPY, but is not a trusted overlay.",
                    window->getName().c_str());

native 侧强制所有 spy window 必须同时是 TRUSTED_OVERLAY。这是安全边界:spy window 能旁听触摸流,不能开放给普通应用窗口。

5. InputDispatcher 如何选择普通窗口和 Spy Window

5.1 方法: InputDispatcher::findTouchedWindowAtLocked(...)

文件: frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

cpp 复制代码
std::pair<sp<WindowInfoHandle>, std::vector<InputTarget>>
InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, float x, float y, bool isStylus,
                                           bool ignoreDragWindow) const {
    // Traverse windows from front to back to find touched window.
    const auto& windowHandles = getWindowHandlesLocked(displayId);
    for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
        const WindowInfo& info = *windowHandle->getInfo();
        if (!info.isSpy() &&
            windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) {
            return {windowHandle, outsideTargets};
        }
    }
    return {nullptr, {}};
}

普通触摸目标明确排除 spy window:

cpp 复制代码
!info.isSpy()

因此 spy window 不会成为常规 foreground touch target。

5.2 方法: InputDispatcher::findTouchedSpyWindowsAtLocked(...)

文件: frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

cpp 复制代码
std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAtLocked(
        int32_t displayId, float x, float y, bool isStylus) const {
    // Traverse windows from front to back and gather the touched spy windows.
    std::vector<sp<WindowInfoHandle>> spyWindows;
    const auto& windowHandles = getWindowHandlesLocked(displayId);
    for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
        const WindowInfo& info = *windowHandle->getInfo();

        if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) {
            continue;
        }
        if (!info.isSpy()) {
            // The first touched non-spy window was found, so return the spy windows touched so far.
            return spyWindows;
        }
        spyWindows.push_back(windowHandle);
    }
    return spyWindows;
}

这个方法体现了 spy window 的 Z-order 规则:

  • 从上到下遍历窗口。
  • 命中的 spy window 会被收集。
  • 一旦遇到第一个命中的非 spy window,就停止并返回已经收集的 spy windows。
  • 所以非 spy window 下面的 spy window 不会收到该事件。

5.3 方法: InputDispatcher::findTouchedWindowTargetsLocked(...)

文件: frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

cpp 复制代码
auto [newTouchedWindowHandle, outsideTargets] =
        findTouchedWindowAtLocked(displayId, x, y, isStylus);

std::vector<sp<WindowInfoHandle>> newTouchedWindows =
        findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus);
if (newTouchedWindowHandle != nullptr) {
    // Process the foreground window first so that it is the first to receive the event.
    newTouchedWindows.insert(newTouchedWindows.begin(), newTouchedWindowHandle);
}

for (const sp<WindowInfoHandle>& windowHandle : newTouchedWindows) {
    ftl::Flags<InputTarget::Flags> targetFlags = InputTarget::Flags::DISPATCH_AS_IS;

    if (canReceiveForegroundTouches(*windowHandle->getInfo())) {
        targetFlags |= InputTarget::Flags::FOREGROUND;
    }

    tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, entry.deviceId, pointerIds,
                                     isDownOrPointerDown
                                             ? std::make_optional(entry.eventTime)
                                             : std::nullopt);
}

调用顺序是:

text 复制代码
findTouchedWindowTargetsLocked()
  -> findTouchedWindowAtLocked()       // 找真正的 foreground window,排除 spy
  -> findTouchedSpyWindowsAtLocked()   // 找 foreground window 上方的 spy windows
  -> insert foreground at begin        // foreground 优先分发
  -> addOrUpdateWindow(...)            // 都加入 TouchState

因此 spy window 会收到同一条 pointer stream,但不会改变真正的 foreground window 选择。

6. Spy Window 为什么不是 Foreground Touch Target

方法: canReceiveForegroundTouches(...)

文件: frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

cpp 复制代码
bool canReceiveForegroundTouches(const WindowInfo& info) {
    // A non-touchable window can still receive touch events (e.g. in the case of
    // STYLUS_INTERCEPTOR), so prevent such windows from receiving foreground events for touches.
    return !info.inputConfig.test(gui::WindowInfo::InputConfig::NOT_TOUCHABLE) && !info.isSpy();
}

spy window 可以收事件副本,但不会带 InputTarget::Flags::FOREGROUND。这保证了系统手势监听不会破坏普通 app 的常规触摸目标选择。

7. pilferPointers: 从旁听变成接管

7.1 方法: InputMonitor.pilferPointers()

文件: frameworks/base/core/java/android/view/InputMonitor.java

java 复制代码
/**
 * Takes all of the current pointer events streams that are currently being sent to this
 * monitor and generates appropriate cancellations for the windows that would normally get
 * them.
 */
public void pilferPointers() {
    try {
        mHost.pilferPointers();
    } catch (RemoteException e) {
        e.rethrowFromSystemServer();
    }
}

这是 Java InputMonitor 对外暴露的抢占接口。

7.2 方法: InputManagerService.InputMonitorHost.pilferPointers()

文件: frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

java 复制代码
private final class InputMonitorHost extends IInputMonitorHost.Stub {
    private final IBinder mInputChannelToken;

    @Override
    public void pilferPointers() {
        mNative.pilferPointers(mInputChannelToken);
    }
}

Java 层调用会进入 native InputDispatcher::pilferPointers(...)

7.3 方法: InputDispatcher::pilferPointersLocked(...)

文件: frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

cpp 复制代码
status_t InputDispatcher::pilferPointersLocked(const sp<IBinder>& token) {
    auto [statePtr, windowPtr, displayId] = findTouchStateWindowAndDisplayLocked(token);

    TouchState& state = *statePtr;
    TouchedWindow& window = *windowPtr;

    // Send cancel events to all the input channels we're stealing from.
    CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
                               "input channel stole pointer stream");
    std::bitset<MAX_POINTER_ID + 1> pointerIds = window.getTouchingPointers(deviceId);
    options.pointerIds = pointerIds;

    for (const TouchedWindow& w : state.windows) {
        const std::shared_ptr<InputChannel> channel =
                getInputChannelLocked(w.windowHandle->getToken());
        if (channel != nullptr && channel->getConnectionToken() != token) {
            synthesizeCancelationEventsForInputChannelLocked(channel, options);
        }
    }

    // Prevent the gesture from being sent to any other windows.
    window.addPilferingPointers(deviceId, pointerIds);

    state.cancelPointersForWindowsExcept(deviceId, pointerIds, token);
    return OK;
}

行为总结:

  1. 找到调用者 token 对应的当前触摸窗口。
  2. 取出这个窗口正在接收的 pointer ids。
  3. 给其他正在接收这些 pointer 的窗口合成 ACTION_CANCEL
  4. 将这些 pointer 标记为 pilfering。
  5. 后续同一 pointer stream 只继续发给 pilfering 窗口。

7.4 方法: InputDispatcher::findTouchedWindowTargetsLocked(...)

文件: frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

cpp 复制代码
// If a window is already pilfering some pointers, give it this new pointer as well and
// make it pilfering. This will prevent other non-spy windows from getting this pointer,
// which is a specific behaviour that we want.
const int32_t pointerId = entry.pointerProperties[pointerIndex].id;
for (TouchedWindow& touchedWindow : tempTouchState.windows) {
    if (touchedWindow.hasTouchingPointer(entry.deviceId, pointerId) &&
        touchedWindow.hasPilferingPointers(entry.deviceId)) {
        touchedWindow.addPilferingPointer(entry.deviceId, pointerId);
    }
}

// Restrict all pilfered pointers to the pilfering windows.
tempTouchState.cancelPointersForNonPilferingWindows();

这段处理多指场景:如果某个 spy window 已经 pilfer 了一部分 pointer,新落下且也命中它的 pointer 会继续归它接管。

8. 使用场景和真实代码

8.1 返回手势

方法: EdgeBackGestureHandler.onInputEvent(...) 内部处理逻辑

文件: frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java

java 复制代码
} else if (dx > dy && dx > mTouchSlop) {
    if (mAllowGesture) {
        mThresholdCrossed = true;
        // Capture inputs
        mInputMonitor.pilferPointers();
        mInputEventReceiver.setBatchingEnabled(true);
    }
}

调用链:

text 复制代码
SystemUI EdgeBackGestureHandler
  -> InputManager.monitorGestureInput(...)
  -> InputMonitor receives pointer stream through spy window
  -> gesture crosses threshold
  -> InputMonitor.pilferPointers()
  -> InputDispatcher sends ACTION_CANCEL to app
  -> remaining MOVE/UP go to SystemUI

适合场景:一开始不能拦截 app,否则边缘普通点击会被破坏;只有确认是返回手势后才抢占。

8.2 Clipboard Overlay 外部点击

方法: ClipboardOverlayController.monitorOutsideTouches()

文件: frameworks/base/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java

java 复制代码
private void monitorOutsideTouches() {
    InputManager inputManager = mContext.getSystemService(InputManager.class);
    mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
    mInputEventReceiver = new InputEventReceiver(
            mInputMonitor.getInputChannel(), Looper.getMainLooper()) {
        @Override
        public void onInputEvent(InputEvent event) {
            if (event instanceof MotionEvent) {
                MotionEvent motionEvent = (MotionEvent) event;
                if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
                    if (!mView.isInTouchRegion(
                            (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) {
                        animateOut();
                    }
                }
            }
        }
    };
}

适合场景:overlay 想知道用户是否点击了外部区域,用于关闭 UI,但不需要从一开始吞掉 app 的触摸。

8.3 UDFPS Overlay

方法/属性: UdfpsControllerOverlay.coreLayoutParams

文件: frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt

kotlin 复制代码
if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
    inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
}

适合场景:已有系统 overlay 窗口需要监听触摸,同时不应该阻断底层窗口的普通输入。

9. 和 monitorInput Global Monitor 的区别

9.1 方法: InputManagerService.monitorInput(...)

文件: frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

java 复制代码
/**
 * Creates an input channel that will receive all input from the input dispatcher.
 */
public InputChannel monitorInput(String inputChannelName, int displayId) {
    Objects.requireNonNull(inputChannelName, "inputChannelName not be null");

    if (displayId < Display.DEFAULT_DISPLAY) {
        throw new IllegalArgumentException("displayId must >= 0.");
    }

    return mNative.createInputMonitor(displayId, inputChannelName, Binder.getCallingPid());
}

这是老式 global monitor 路径,返回的是 InputChannel,不是 android.view.InputMonitor

9.2 方法: InputDispatcher::createInputMonitor(...)

文件: frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

cpp 复制代码
Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputMonitor(int32_t displayId,
                                                                          const std::string& name,
                                                                          gui::Pid pid) {
    openInputChannelPair(name, serverChannel, clientChannel);

    std::shared_ptr<Connection> connection =
            std::make_shared<Connection>(serverChannel, /*monitor=*/true, mIdGenerator);

    mConnectionsByToken.emplace(token, connection);
    mGlobalMonitorsByDisplay[displayId].emplace_back(serverChannel, pid);
    mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, ...);

    return clientChannel;
}

它把 channel 放进 mGlobalMonitorsByDisplay,不创建 InputWindowHandle,也不进入窗口 Z-order。

9.3 方法: InputDispatcher::addGlobalMonitoringTargetsLocked(...)

文件: frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

cpp 复制代码
void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets,
                                                       int32_t displayId) {
    auto monitorsIt = mGlobalMonitorsByDisplay.find(displayId);
    if (monitorsIt == mGlobalMonitorsByDisplay.end()) return;

    for (const Monitor& monitor : selectResponsiveMonitorsLocked(monitorsIt->second)) {
        InputTarget target;
        target.inputChannel = monitor.inputChannel;
        target.flags = InputTarget::Flags::DISPATCH_AS_IS;
        inputTargets.push_back(target);
    }
}

global monitor 是在 key/motion dispatch 阶段额外追加的全局目标,不参与窗口命中测试。

9.4 示例: DisplayContent 创建 PointerEventDispatcher

方法/构造流程: DisplayContent.DisplayContent(...)

文件: frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java

java 复制代码
final InputChannel inputChannel = mWmService.mInputManager.monitorInput(
        "PointerEventDispatcher" + mDisplayId, mDisplayId);
mPointerEventDispatcher = new PointerEventDispatcher(inputChannel);

适合场景:WMS 内部希望观察 display 上的 pointer event,例如 task tap detection、鼠标位置追踪等。

10. 对比总结

机制 本质 是否是窗口 是否受 Z-order 影响 是否受 touchable region 影响 是否适合 pilfer 典型场景
InputConfig.SPY InputWindow 配置 系统可信 overlay 旁听触摸
monitorGestureInput() 返回的 InputMonitor Java API,底层是 GestureMonitorSpyWindow 返回手势、Shell 手势、overlay 外部点击
monitorInput() global monitor native global monitor channel 通常不适合 WMS 内部全局 pointer 观察

简化选择:

  • 已经有系统 overlay 窗口,并希望它旁听触摸:使用 INPUT_FEATURE_SPY
  • 系统组件要监听 display 上的手势,并可能识别后抢占:使用 InputManager.monitorGestureInput()
  • WMS 内部需要全局观察输入,不需要窗口命中语义:使用 monitorInput() global monitor。
相关推荐
dalancon5 小时前
InputDispatcher派发事件,查找目标窗口
android
我命由我123455 小时前
Android Framework P3 - MediaServer 进程、认识 ServiceManager 进程
android·c语言·开发语言·c++·visualstudio·visual studio·android runtime
天才少年曾牛6 小时前
Android14 新增系统服务后,应用调用出现 “hidden api” 警告的原因与解决方案
android·frameworks
赏金术士6 小时前
Jetpack Compose 底部导航实战教程(完整版)
android·kotlin·compose
随遇丿而安7 小时前
第5周:XML 资源、样式和主题,真正解决的是“页面以后还改不改得动”
android
zh_xuan7 小时前
Android 获取系统内存页大小:sysconf(_SC_PAGESIZE) 与 JNI 实现
android·jni·ndk·内存页大小
fundroid9 小时前
Google I/O 2026 | Android 全面进化:从操作系统到“智能中枢”
android·jetpack compose·google i/o 2026
zh_xuan9 小时前
Android 复用 .so 库:通过 jniLibs 集成预编译二进制库(获取 Page Size )
android·jni·ndk·内存页大小
匆忙拥挤repeat10 小时前
Android Compose 约束布局
android