InputDispatcher::findTouchedWindowTargetsLocked 流程分析
本文分析 AOSP 14 frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp:2236 的 InputDispatcher::findTouchedWindowTargetsLocked(),重点说明触摸事件如何查找 window target,以及 TouchOcclusionInfo 如何参与可信触摸判定。
方法入口与调用上下文
dispatchMotionLocked() 在处理 pointer motion event 时调用该方法:
cpp
// InputDispatcher.cpp:1845-1865
const bool isPointerEvent = isFromSource(entry->source, AINPUT_SOURCE_CLASS_POINTER);
...
inputTargets =
findTouchedWindowTargetsLocked(currentTime, *entry, &conflictingPointerActions,
/*byref*/ injectionResult);
因此 findTouchedWindowTargetsLocked() 的职责是把一个 MotionEntry 转换成一组 InputTarget,并维护 mTouchStatesByDisplay 中当前 display 的触摸状态。
方法入口先取出 displayId/action/maskedAction,再把旧的 display touch state 拷贝到 tempTouchState:
cpp
// InputDispatcher.cpp:2251-2261
const TouchState* oldState = nullptr;
TouchState tempTouchState;
if (const auto it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) {
oldState = &(it->second);
tempTouchState = *oldState;
}
bool isSplit = shouldSplitTouch(tempTouchState, entry);
这里使用临时状态是为了在确认注入权限、目标有效、事件可分发之前,不提前污染真实 mTouchStatesByDisplay。
整体流程概览
该方法可以分成两条主路径:
- 新手势、hover、scroll,或 split touch 下的
POINTER_DOWN:重新根据坐标查找触摸窗口。 - move、up、cancel,或非 split 的
POINTER_DOWN:复用已有TouchState,必要时处理 slippery window 切换。
关键分支在 InputDispatcher.cpp:2313:
cpp
// InputDispatcher.cpp:2313-2315
if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
/* Case 1: New splittable pointer going down, or need target for hover or scroll. */
const auto [x, y] = resolveTouchedPosition(entry);
resolveTouchedPosition() 对 mouse 使用 cursor 坐标,对其他 pointer 使用 action pointer 坐标:
cpp
// InputDispatcher.cpp:598-607
if (isFromMouse) {
return {entry.xCursorPosition, entry.yCursorPosition};
}
const int32_t pointerIndex = getMotionEventActionPointerIndex(entry.action);
return {entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X),
entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y)};
Case 1: 新手势与可拆分 POINTER_DOWN 的目标查找
1. 查找前景触摸窗口
新目标查找从 findTouchedWindowAtLocked() 开始:
cpp
// InputDispatcher.cpp:2315-2322
const auto [x, y] = resolveTouchedPosition(entry);
const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
const bool isStylus = isPointerFromStylus(entry, pointerIndex);
auto [newTouchedWindowHandle, outsideTargets] =
findTouchedWindowAtLocked(displayId, x, y, isStylus);
findTouchedWindowAtLocked() 按当前 display 的窗口 Z 序从前到后遍历。它返回第一个"非 spy 且可接受该点触摸"的窗口,同时在到达目标窗口前收集 WATCH_OUTSIDE_TOUCH 窗口作为 outside targets:
cpp
// InputDispatcher.cpp:1241-1264
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};
}
if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
addWindowTargetLocked(windowHandle, InputTarget::Flags::DISPATCH_AS_OUTSIDE,
/*pointerIds=*/{}, /*firstDownTimeInTarget=*/std::nullopt,
outsideTargets);
}
}
return {nullptr, {}};
命中判断由 windowAcceptsTouchAt() 完成,核心条件是:
- window 必须属于目标 display。
- window 不能是
NOT_VISIBLE。 - window 不能是
NOT_TOUCHABLE,除非 stylus interceptor 可拦截 stylus。 - 坐标经过 display transform 后必须落在
touchableRegion内。
代码证据:
cpp
// InputDispatcher.cpp:521-545
if (windowInfo.displayId != displayId ||
inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) {
return false;
}
const bool windowCanInterceptTouch = isStylus && windowInfo.interceptsStylus();
if (inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) && !windowCanInterceptTouch) {
return false;
}
const auto touchableRegion = displayTransform.transform(windowInfo.touchableRegion);
const auto p = displayTransform.transform(x, y);
if (!touchableRegion.contains(std::floor(p.x), std::floor(p.y))) {
return false;
}
return true;
如果没有找到新窗口,方法会尝试复用当前手势中的第一个 foreground window:
cpp
// InputDispatcher.cpp:2326-2331
if (newTouchedWindowHandle == nullptr) {
ALOGD("No new touched window ...");
newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle();
}
2. 校验 targeted injection 与 split touch
找到候选窗口后,先校验 targeted injection:
cpp
// InputDispatcher.cpp:2333-2339
if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
newTouchedWindowHandle = nullptr;
return {};
}
随后根据窗口能力决定是否允许 split touch:
cpp
// InputDispatcher.cpp:2341-2356
if (newTouchedWindowHandle != nullptr) {
if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
isSplit = !isFromMouse;
} else if (isSplit) {
newTouchedWindowHandle = nullptr;
}
} else {
isSplit = !isFromMouse;
}
含义是:mouse 永不 split;新窗口支持 split 时才允许拆分;如果当前手势已经 split,但新窗口不支持 split,就忽略这个新窗口。
3. 补充 spy windows
前景窗口之外,方法还会查找 spy windows:
cpp
// InputDispatcher.cpp:2358-2363
std::vector<sp<WindowInfoHandle>> newTouchedWindows =
findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus);
if (newTouchedWindowHandle != nullptr) {
newTouchedWindows.insert(newTouchedWindows.begin(), newTouchedWindowHandle);
}
findTouchedSpyWindowsAtLocked() 同样按 Z 序遍历,但只收集命中点上的 spy window;一旦遇到第一个命中的非 spy window 就返回:
cpp
// InputDispatcher.cpp:1267-1284
for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
const WindowInfo& info = *windowHandle->getInfo();
if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) {
continue;
}
if (!info.isSpy()) {
return spyWindows;
}
spyWindows.push_back(windowHandle);
}
return spyWindows;
因此最终 newTouchedWindows 的顺序是:foreground touched window 在最前,后面跟着它上方命中的 spy windows。这样 foreground window 会优先收到事件。
4. 过滤不可接收窗口并写入 TouchState
遍历 newTouchedWindows 时,每个窗口先经过 canWindowReceiveMotionLocked():
cpp
// InputDispatcher.cpp:2373-2376
for (const sp<WindowInfoHandle>& windowHandle : newTouchedWindows) {
if (!canWindowReceiveMotionLocked(windowHandle, entry)) {
continue;
}
通过过滤后,为目标设置 flags:
cpp
// InputDispatcher.cpp:2385-2400
ftl::Flags<InputTarget::Flags> targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
if (canReceiveForegroundTouches(*windowHandle->getInfo())) {
targetFlags |= InputTarget::Flags::FOREGROUND;
}
if (isSplit) {
targetFlags |= InputTarget::Flags::SPLIT;
}
if (isWindowObscuredAtPointLocked(windowHandle, x, y)) {
targetFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED;
} else if (isWindowObscuredLocked(windowHandle)) {
targetFlags |= InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
}
然后写入临时触摸状态:
cpp
// InputDispatcher.cpp:2402-2418
std::bitset<MAX_POINTER_ID + 1> pointerIds;
if (!isHoverAction) {
pointerIds.set(entry.pointerProperties[pointerIndex].id);
}
...
tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, entry.deviceId, pointerIds,
isDownOrPointerDown
? std::make_optional(entry.eventTime)
: std::nullopt);
如果 foreground window 设置了 DUPLICATE_TOUCH_TO_WALLPAPER,还会查找下方 wallpaper 并加入 tempTouchState:
cpp
// InputDispatcher.cpp:2426-2440
if (targetFlags.test(InputTarget::Flags::FOREGROUND) &&
windowHandle->getInfo()->inputConfig.test(
gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
sp<WindowInfoHandle> wallpaper = findWallpaperWindowBelow(windowHandle);
if (wallpaper != nullptr) {
...
tempTouchState.addOrUpdateWindow(wallpaper, wallpaperFlags, entry.deviceId,
pointerIds, entry.eventTime);
}
}
最后处理 pilfer pointers:如果某个窗口已经 pilfer 部分 pointer,新 pointer 也属于它,则把新 pointer 也标记为 pilfer,并取消非 pilfering 窗口的相关 pointer:
cpp
// InputDispatcher.cpp:2446-2461
for (TouchedWindow& touchedWindow : tempTouchState.windows) {
if (touchedWindow.hasTouchingPointer(entry.deviceId, pointerId) &&
touchedWindow.hasPilferingPointers(entry.deviceId)) {
touchedWindow.addPilferingPointer(entry.deviceId, pointerId);
}
}
tempTouchState.cancelPointersForNonPilferingWindows();
Case 2: MOVE / UP / CANCEL / 非拆分 POINTER_DOWN
非新目标路径不会重新查找普通 window target,而是复用 tempTouchState 中已锁定的窗口集合。
首先,如果没有 pointer down 状态且不是 hover exit,事件会被丢弃:
cpp
// InputDispatcher.cpp:2463-2472
if (!tempTouchState.isDown() && maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
outInjectionResult = InputEventInjectionResult::FAILED;
return {};
}
随后处理 drag 事件:
cpp
// InputDispatcher.cpp:2488
addDragEventLocked(entry);
特殊情况是 slippery window:单指 move 且当前 foreground window 是 slippery 时,会根据当前位置重新调用 findTouchedWindowAtLocked() 查找新窗口。如果新旧窗口 token 不同,则给旧窗口发送 DISPATCH_AS_SLIPPERY_EXIT,给新窗口发送 DISPATCH_AS_SLIPPERY_ENTER,并更新 tempTouchState:
cpp
// InputDispatcher.cpp:2490-2514
if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.pointerCount == 1 &&
tempTouchState.isSlippery()) {
const auto [x, y] = resolveTouchedPosition(entry);
sp<WindowInfoHandle> oldTouchedWindowHandle =
tempTouchState.getFirstForegroundWindowHandle();
auto [newTouchedWindowHandle, _] = findTouchedWindowAtLocked(displayId, x, y, isStylus);
cpp
// InputDispatcher.cpp:2524-2556
addWindowTargetLocked(oldTouchedWindowHandle,
InputTarget::Flags::DISPATCH_AS_SLIPPERY_EXIT, pointerIds,
touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
...
ftl::Flags<InputTarget::Flags> targetFlags =
InputTarget::Flags::DISPATCH_AS_SLIPPERY_ENTER;
...
tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags,
entry.deviceId, pointerIds, entry.eventTime);
...
tempTouchState.removeTouchingPointerFromWindow(entry.deviceId, pointerId,
oldTouchedWindowHandle);
如果非 split 场景收到 POINTER_DOWN,新增 pointer 会被加到所有已触摸窗口:
cpp
// InputDispatcher.cpp:2560-2572
if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
for (size_t i = 0; i < tempTouchState.windows.size(); i++) {
TouchedWindow& touchedWindow = tempTouchState.windows[i];
...
touchedWindow.addTouchingPointer(entry.deviceId,
entry.pointerProperties[pointerIndex].id);
}
}
InputTarget 生成链路
findTouchedWindowTargetsLocked() 并不是在查找窗口时立即把所有窗口都放入最终 targets。核心做法是:
- 先把窗口、flags、pointerIds、downTime 写入
tempTouchState。 - 后面统一遍历
tempTouchState.windows,把每个仍有 touching 或 hovering pointer 的窗口转换成InputTarget。
代码证据:
cpp
// InputDispatcher.cpp:2638-2650
for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
if (!touchedWindow.hasTouchingPointers(entry.deviceId) &&
!touchedWindow.hasHoveringPointers(entry.deviceId)) {
continue;
}
addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
touchedWindow.getTouchingPointers(entry.deviceId),
touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
}
addWindowTargetLocked() 会按 connection token 合并同一个 window target;如果还不存在,就先通过 createInputTargetLocked() 创建:
cpp
// InputDispatcher.cpp:2870-2898
std::vector<InputTarget>::iterator it =
std::find_if(inputTargets.begin(), inputTargets.end(),
[&windowHandle](const InputTarget& inputTarget) {
return inputTarget.inputChannel->getConnectionToken() ==
windowHandle->getToken();
});
...
if (it == inputTargets.end()) {
std::optional<InputTarget> target =
createInputTargetLocked(windowHandle, targetFlags, firstDownTimeInTarget);
if (!target) {
return;
}
inputTargets.push_back(*target);
it = inputTargets.end() - 1;
}
...
it->addPointers(pointerIds, windowInfo->transform);
createInputTargetLocked() 则绑定 input channel、window handle、flags、scale、downTime 和 display transform:
cpp
// InputDispatcher.cpp:2845-2868
std::shared_ptr<InputChannel> inputChannel = getInputChannelLocked(windowHandle->getToken());
if (inputChannel == nullptr) {
return {};
}
InputTarget inputTarget;
inputTarget.inputChannel = inputChannel;
inputTarget.windowHandle = windowHandle;
inputTarget.flags = targetFlags;
inputTarget.globalScaleFactor = windowHandle->getInfo()->globalScaleFactor;
inputTarget.firstDownTimeInTarget = firstDownTimeInTarget;
...
return inputTarget;
最后,如果没有任何 target,或者所有 target 都只是 ACTION_OUTSIDE,事件会失败:
cpp
// InputDispatcher.cpp:2664-2679
if (targets.empty()) {
outInjectionResult = InputEventInjectionResult::FAILED;
return {};
}
if (std::all_of(targets.begin(), targets.end(), [](const InputTarget& target) {
return target.flags.test(InputTarget::Flags::DISPATCH_AS_OUTSIDE);
})) {
outInjectionResult = InputEventInjectionResult::FAILED;
return {};
}
成功后才清理临时状态、更新 mTouchStatesByDisplay:
cpp
// InputDispatcher.cpp:2681-2737
outInjectionResult = InputEventInjectionResult::SUCCEEDED;
tempTouchState.filterNonAsIsTouchWindows();
...
if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
if (displayId >= 0) {
tempTouchState.clearWindowsWithoutPointers();
mTouchStatesByDisplay[displayId] = tempTouchState;
} else {
mTouchStatesByDisplay.erase(displayId);
}
}
TouchOcclusionInfo 计算与拦截逻辑
一个容易误读的点是:findTouchedWindowTargetsLocked() 本身没有直接调用 computeTouchOcclusionInfoLocked()。遮挡可信度检查发生在它调用的 canWindowReceiveMotionLocked() 中:
cpp
// InputDispatcher.cpp:2373-2376
for (const sp<WindowInfoHandle>& windowHandle : newTouchedWindows) {
if (!canWindowReceiveMotionLocked(windowHandle, entry)) {
continue;
}
canWindowReceiveMotionLocked() 在完成 targeted injection、暂停分发、input channel、connection responsive 等检查后,计算触摸遮挡信息:
cpp
// InputDispatcher.cpp:4997-5010
const auto [x, y] = resolveTouchedPosition(motionEntry);
TouchOcclusionInfo occlusionInfo = computeTouchOcclusionInfoLocked(window, x, y);
if (!isTouchTrustedLocked(occlusionInfo)) {
...
ALOGW("Dropping untrusted touch event due to %s/%s", occlusionInfo.obscuringPackage.c_str(),
occlusionInfo.obscuringUid.toString().c_str());
return false;
}
也就是说,遮挡计算会影响窗口能否进入 tempTouchState,从而影响最终是否生成 InputTarget。
TouchOcclusionInfo 的结构定义如下:
cpp
// InputDispatcher.h:573-579
struct TouchOcclusionInfo {
bool hasBlockingOcclusion;
float obscuringOpacity;
std::string obscuringPackage;
gui::Uid obscuringUid = gui::Uid::INVALID;
std::vector<std::string> debugInfo;
};
1. 哪些窗口有资格成为遮挡窗口
computeTouchOcclusionInfoLocked() 内部依赖 canBeObscuredBy() 过滤候选遮挡窗口:
cpp
// InputDispatcher.cpp:2924-2952
static bool canBeObscuredBy(const sp<WindowInfoHandle>& windowHandle,
const sp<WindowInfoHandle>& otherHandle) {
if (haveSameToken(windowHandle, otherHandle)) {
return false;
}
...
if (otherInfo->inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) {
return false;
} else if (otherInfo->alpha == 0 &&
otherInfo->inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE)) {
return false;
} else if (info->ownerUid == otherInfo->ownerUid) {
return false;
} else if (otherInfo->inputConfig.test(gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)) {
return false;
} else if (otherInfo->displayId != info->displayId) {
return false;
}
return true;
}
因此,同 token、不可见、透明且不可触摸、同 UID、trusted overlay、不同 display 的窗口都不会作为不可信遮挡来源。
2. 按 Z 序只检查目标窗口上方的窗口
遮挡计算按 display 的 windowHandles 从前到后遍历,遇到目标窗口后立即停止:
cpp
// InputDispatcher.cpp:2972-2988
const WindowInfo* windowInfo = windowHandle->getInfo();
int32_t displayId = windowInfo->displayId;
const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
TouchOcclusionInfo info;
...
for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
if (windowHandle == otherHandle) {
break; // All future windows are below us. Exit early.
}
const WindowInfo* otherInfo = otherHandle->getInfo();
if (canBeObscuredBy(windowHandle, otherHandle) && otherInfo->frameContainsPoint(x, y) &&
!haveSameApplicationToken(windowInfo, otherInfo)) {
这里同时要求遮挡窗口的 frame 包含触摸点,并且不是同 application token。
3. BLOCK_UNTRUSTED 直接拦截
如果上方窗口的 touchOcclusionMode 是 BLOCK_UNTRUSTED,则直接设置 blocking occlusion 并结束遍历:
cpp
// InputDispatcher.cpp:2993-3001
if (otherInfo->touchOcclusionMode == TouchOcclusionMode::BLOCK_UNTRUSTED) {
info.hasBlockingOcclusion = true;
info.obscuringUid = otherInfo->ownerUid;
info.obscuringPackage = otherInfo->packageName;
break;
}
之后 isTouchTrustedLocked() 会因为 hasBlockingOcclusion 返回 false:
cpp
// InputDispatcher.cpp:3041-3046
if (occlusionInfo.hasBlockingOcclusion) {
ALOGW("Untrusted touch due to occlusion by %s/%s", ...);
return false;
}
4. USE_OPACITY 按 UID 合成透明度
如果遮挡窗口的模式是 USE_OPACITY,则按 owner UID 累积透明度:
cpp
// InputDispatcher.cpp:3002-3015
if (otherInfo->touchOcclusionMode == TouchOcclusionMode::USE_OPACITY) {
const auto uid = otherInfo->ownerUid;
float opacity =
(opacityByUid.find(uid) == opacityByUid.end()) ? 0 : opacityByUid[uid];
// opacity(A, B) = 1 - [1 - opacity(A)] * [1 - opacity(B)]
opacity = 1 - (1 - opacity) * (1 - otherInfo->alpha);
opacityByUid[uid] = opacity;
if (opacity > info.obscuringOpacity) {
info.obscuringOpacity = opacity;
info.obscuringUid = uid;
info.obscuringPackage = otherInfo->packageName;
}
}
这段逻辑用于计算"触摸点上方,来自同一个 UID 的不可信窗口叠加后的遮挡透明度",最终判断触摸事件是否应该被拦截。
核心代码是:
opacity = 1 - (1 - opacity) * (1 - otherInfo->alpha);
含义是:多个半透明窗口叠在一起时,总遮挡度不是简单相加,而是按"剩余可见部分"相乘。
假设:
opacity = 当前这个 UID 已经累计的遮挡不透明度 otherInfo->alpha = 当前窗口的不透明度
那么:
1 - opacity
表示之前窗口叠加后还剩多少"没被遮住"。
1 - otherInfo->alpha
表示当前窗口自身还透出多少。
两个相乘:
(1 - opacity) * (1 - otherInfo->alpha)
表示经过之前窗口和当前窗口之后,最终还剩多少是透明可见的。
所以总不透明度就是:
1 - 最终剩余透明度
也就是:
1 - (1 - opacity) * (1 - otherInfo->alpha)
举例:
如果同一个 UID 有两个窗口都盖在触摸点上:
窗口 A alpha = 0.5 窗口 B alpha = 0.5
第一次:
opacity = 1 - (1 - 0) * (1 - 0.5) = 1 - 1 * 0.5 = 0.5
第二次:
opacity = 1 - (1 - 0.5) * (1 - 0.5) = 1 - 0.5 * 0.5 = 0.75
所以两个 50% 不透明窗口叠加后,总遮挡不透明度是 0.75,不是 1.0。
再举一个例子:
窗口 A alpha = 0.3 窗口 B alpha = 0.4 窗口 C alpha = 0.5
累计过程:
A 后: 1 - (1 - 0) * (1 - 0.3) = 0.3 B 后: 1 - (1 - 0.3) * (1 - 0.4) = 0.58 C 后: 1 - (1 - 0.58)* (1 - 0.5) = 0.79
最终这个 UID 的遮挡不透明度是 0.79。
这里特意按 UID 聚合:
std::map<gui::Uid, float> opacityByUid;
也就是说,系统不是把所有应用的窗口透明度混在一起算,而是分别计算每个 UID 的累计遮挡度。最后记录遮挡度最大的那个 UID:
if (opacity > info.obscuringOpacity) { info.obscuringOpacity = opacity; info.obscuringUid = uid; info.obscuringPackage = otherInfo->packageName; }
后续会拿 info.obscuringOpacity 和系统阈值 mMaximumObscuringOpacityForTouch 比较。如果某个 UID 的累计遮挡透明度超过阈值,触摸就会被认为受到不可信 窗口遮挡,可能被阻止传递给目标窗口
最终可信判定如下:
cpp
// InputDispatcher.cpp:3047-3054
if (occlusionInfo.obscuringOpacity > mMaximumObscuringOpacityForTouch) {
ALOGW("Untrusted touch due to occlusion by %s/%s ...", ...);
return false;
}
return true;
所以 TouchOcclusionInfo 有两个拦截条件:
hasBlockingOcclusion == true:存在BLOCK_UNTRUSTED遮挡窗口。obscuringOpacity > mMaximumObscuringOpacityForTouch:某个 UID 的USE_OPACITY遮挡窗口合成透明度超过阈值。
关键结论
findTouchedWindowTargetsLocked()是 pointer motion event 到InputTarget的核心转换函数。- 新手势路径会先通过
findTouchedWindowAtLocked()找第一个可触摸的非 spy foreground window,再通过findTouchedSpyWindowsAtLocked()补充 spy windows。 findTouchedWindowAtLocked()使用 Z 序前到后查找,命中条件最终由windowAcceptsTouchAt()判断。TouchOcclusionInfo不在findTouchedWindowTargetsLocked()中直接计算,而是在canWindowReceiveMotionLocked()中作为窗口可接收性检查的一部分被计算。- 遮挡检查会在窗口加入
tempTouchState前执行;不可信窗口不会进入后续TouchState -> InputTarget输出链路。 - 最终
InputTarget主要从tempTouchState.windows统一生成,addWindowTargetLocked()负责创建或合并 target,并写入 pointer transform。 - 成功分发后才把
tempTouchState保存回mTouchStatesByDisplay,这保证了失败、权限不匹配或无 target 的事件不会污染后续手势状态。