惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
Security Latest
Security Latest
P
Proofpoint News Feed
GbyAI
GbyAI
PCI Perspectives
PCI Perspectives
博客园 - Franky
N
Netflix TechBlog - Medium
博客园_首页
WordPress大学
WordPress大学
K
Kaspersky official blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Vercel News
Vercel News
T
Threatpost
The Hacker News
The Hacker News
H
Help Net Security
S
Securelist
Recent Announcements
Recent Announcements
腾讯CDC
T
Tailwind CSS Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Engineering at Meta
Engineering at Meta
C
Cisco Blogs
V
V2EX
C
Check Point Blog
S
Schneier on Security
Cyberwarzone
Cyberwarzone
C
Cybersecurity and Infrastructure Security Agency CISA
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog RSS Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Jina AI
Jina AI
M
MIT News - Artificial intelligence
T
Threat Research - Cisco Blogs
博客园 - 叶小钗
A
Arctic Wolf
AWS News Blog
AWS News Blog
Latest news
Latest news
Martin Fowler
Martin Fowler
Recorded Future
Recorded Future
Last Week in AI
Last Week in AI
The GitHub Blog
The GitHub Blog
小众软件
小众软件
B
Blog
aimingoo的专栏
aimingoo的专栏
C
Cyber Attacks, Cyber Crime and Cyber Security
V
Visual Studio Blog
P
Palo Alto Networks Blog
Spread Privacy
Spread Privacy

掘金

Win 安装Claude Code FastAPI 的 CORSMiddleware 跨域中间件 Java 自研 ReAct Agent 半年后,我用 LangGraph 验证了这些设计取舍 🚀AI编程工作流终极形态:GitNexus!零Token消耗实现代码知识图谱化!让Claude Code和Codex拥有上帝视角彻底告别盲目改代码,复杂项目重 LeetCode 72. 编辑距离:动态规划经典题解 被The Graph的GraphQL查询坑了三天,我用一个真实DeFi项目把链上数据索引彻底搞懂了 (AI) 编写简单 AI 助手 (ds-agent) 别再让 pnpm 跟着 nvm 跑了!独立安装终极指南 Claude Code 为什么这么顺?Anthropic 最新复盘:真正撑住它的不是模型,而是缓存 从 /simplify 指令深挖 Claude Code 多 Agent 协同机制 Function-Calling与工具使用 新手上路(六):Claude code装上ECC全家桶:38 个子代理、156 个技能、生产级 Hooks 与 Rules 体系 我在 Claude、Kimi、opencode 三个 AI 之间搭了一条自动协作管道 【技能篇】OpenClaw Skill 详解:给 AI 装上"专业外挂" wagmi v2 多链钱包切换:一个 Uniswap 仿盘项目让我踩了三天坑 两周浅学 RAG 我把 Python re 模块比喻成摸金手套 新手上路(三):Claude Code Skills 装了一堆没用?20+ 个 Skill 横向对比 + 三套组合方案,按需抄 K2.6、DeepSeek V4、GPT-5.5 都来了,组合拳打起来 Claude Code 进阶之路:从记忆系统到子代理编排 [java] 编译之后的记录类(Record Classes)长什么样子(上) 国产大模型能力大比拼,社区有话说 我研读了 500 个 Spring Boot 生产级代码库,90% 都犯了这 7 个致命错误 JAVA重点难点 转发-中央网信办部署开展“清朗·整治AI应用乱象”专项行动 合同同步逻辑 【合并已排序数组的三种实现策略,哪一种更可取?】 30天减20斤挑战:少一斤发100红包(2) 我竟然被JavaScript的隐式类型转换坑了三天! 二十五.Electron 初体验与进阶 本地到生产,解决 AI 全栈最后一公里——构建&部署&运维 程序员创业半年:顺的事、不顺的事,和我一直没想清楚的事 UI组件库elementplus 像使用 Redis 一样操作 LocalStorage 向量检索的流程是怎样的?Embedding 和 Rerank 各自的作用? LangChain DeepAgents 速通指南(七)—— DeepAgents使用Agent Skill 为什么越来越多的大厂抛弃MCP,转向CLI? 【节点】[SquareRoot节点]原理解析与实际应用 juejin.cn juejin.cn 从 “存得下” 到 “算得快”:工业物联网需要新一代时序数据平台越来越多工业用户开始意识到一个问题:**数据是存下来了, - 掘金 放弃 Claude 订阅?我用 8 年前的服务器,强跑 Google 最强开源模型 Gemma 4 真实测评! Python开发者狂喜!200+课时FastAPI全栈实战合集,10大模块持续更新中🔥 从 Claw-Code 看 AI 驱动的大型项目开发:2 人 + 10 个自治 Agent 如何产出 48K 行 Rust 代码 秒级创建实例,火山引擎 Milvus Serverless 让 AI Agent 开发更快更省火山引擎MilvusSer MediaPlayer 播放器架构:NuPlayer 的 Source/Decoder/Renderer 三驾马车 juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn OrbStack:一键将你的 Mac 变为本地服务器 NginxPulse:Nginx日志监控革命!实时洞察Web流量与安全态势的智能利器引言:当Nginx日志成为运维的“数 - 掘金 juejin.cn 大V说’AI替代不了你’,但现实是——用AI的人正在替代你2026年是AI落地的元年,自从Claude Code爆火之后 - 掘金 juejin.cn 你以为是技术问题,其实是流程问题:工程效率的真相引言 在软件工程领域,效率问题始终是团队管理者和工程师们关注的焦点。当项 - 掘金 大模型工程三驾马车:Prompt Engineering、Context Engineering 与 Harness Engineering 深度解析 juejin.cn 4.响应式系统基础:从发布订阅模式的角度理解 Vue3 的数据响应式原理本文从发布订阅模式的核心思想出发,深入剖析了 V - 掘金 慌了!Android 17 取消图标文字,你的 App 可能要找不到了用户终于可以隐藏桌面图标下面的文字了。 这个功能在 juejin.cn 我用 AI 搓了一个"比谁更持久"的微信小游戏,AI实现只用了一天,微信审核却用了一个月!!!起因:一个沙雕想法的诞生 - 掘金 juejin.cn 第12章 工具(Tools)与函数调用(LangChain实战)在前几章中,我们搭建的RAG系统、对话链,核心能力局限 - 掘金 juejin.cn CmComposeUI —— 基于 Kotlin Multiplatform Compose 的 UI 组件库 Android 开发的 AI coding 与 AI debugging在目前整个行业都在大规模使用 AI coding juejin.cn juejin.cn juejin.cn juejin.cn 一文搞懂Harness Engineering与Meta-Harness 越用越强不是广告语:拆解 Hermes Agent 的三层学习机制 P2G-Python字符串方法完全指南-split、join、strip、replace的Python编程利器 AI 周刊【2026.04.06-04.12】:Anthropic 藏起最强模型、AI 社会矛盾激化、"欢乐马"登顶 从 AI Skills 学实战技能(六):让 AI 帮你总结网页、PDF、视频 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 详解 karpathy 的 microgpt:实现一个浏览器运行的 gpt 不用 Tailscale:3 步把 Mac mini 通过 FRP 暴露到公网(稳定开机自启) P2B-Python可迭代对象完全指南-从列表到生成器的Python编程利器 手把手带你部署本地模型,让你Token自由(小白专属) juejin.cn 10分钟掌握 JSON-RPC 协议,面试加分、设计不踩坑 ReAct:让大模型学会边想边做 聊聊AI的发展史,AI的爆发并不是偶然 Python的列表推导式里藏了个坑,差点让我加班到凌晨 重排、重绘与合成——浏览器渲染性能的底层逻辑 podman与docker的区别和生产环境最佳实践 juejin.cn ConcurrentHashMap线程安全实现原理全解析 juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn juejin.cn OpenAI Codex深度解析:终端里的AI代码特工,一个指令重构整个项目 UE5.6 Cesium 插件编译踩坑记录(UE 5.6 + MSVC 14.38 + CMake 3.31)
Android Input Spy Window
dalancon · 2026-05-24 · via 掘金

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

/**
 * 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 从上到下是:

spy1
spy2
appWindow
spy3

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

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

/**
 * 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

/**
 * @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

@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

@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

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 关联的直接证据:

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

/**
 * 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

/**
 * 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

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

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

// 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

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:

!info.isSpy()

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

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

文件: frameworks/native/services/inputflinger/dispatcher/InputDispatcher.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

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

调用顺序是:

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

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

/**
 * 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

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

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

// 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

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

调用链:

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

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

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

/**
 * 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

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

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

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.SPYInputWindow 配置系统可信 overlay 旁听触摸
monitorGestureInput() 返回的 InputMonitorJava API,底层是 GestureMonitorSpyWindow返回手势、Shell 手势、overlay 外部点击
monitorInput() global monitornative global monitor channel通常不适合WMS 内部全局 pointer 观察

简化选择:

  • 已经有系统 overlay 窗口,并希望它旁听触摸:使用 INPUT_FEATURE_SPY
  • 系统组件要监听 display 上的手势,并可能识别后抢占:使用 InputManager.monitorGestureInput()
  • WMS 内部需要全局观察输入,不需要窗口命中语义:使用 monitorInput() global monitor。