InputDispatcher派发事件,查找目标窗口

InputDispatcher::findTouchedWindowTargetsLocked 流程分析

本文分析 AOSP 14 frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp:2236InputDispatcher::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

整体流程概览

该方法可以分成两条主路径:

  1. 新手势、hover、scroll,或 split touch 下的 POINTER_DOWN:重新根据坐标查找触摸窗口。
  2. 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。核心做法是:

  1. 先把窗口、flags、pointerIds、downTime 写入 tempTouchState
  2. 后面统一遍历 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 直接拦截

如果上方窗口的 touchOcclusionModeBLOCK_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 有两个拦截条件:

  1. hasBlockingOcclusion == true:存在 BLOCK_UNTRUSTED 遮挡窗口。
  2. 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 的事件不会污染后续手势状态。
相关推荐
我命由我123454 小时前
Android Framework P3 - MediaServer 进程、认识 ServiceManager 进程
android·c语言·开发语言·c++·visualstudio·visual studio·android runtime
天才少年曾牛5 小时前
Android14 新增系统服务后,应用调用出现 “hidden api” 警告的原因与解决方案
android·frameworks
赏金术士5 小时前
Jetpack Compose 底部导航实战教程(完整版)
android·kotlin·compose
随遇丿而安5 小时前
第5周:XML 资源、样式和主题,真正解决的是“页面以后还改不改得动”
android
zh_xuan6 小时前
Android 获取系统内存页大小:sysconf(_SC_PAGESIZE) 与 JNI 实现
android·jni·ndk·内存页大小
fundroid7 小时前
Google I/O 2026 | Android 全面进化:从操作系统到“智能中枢”
android·jetpack compose·google i/o 2026
zh_xuan8 小时前
Android 复用 .so 库:通过 jniLibs 集成预编译二进制库(获取 Page Size )
android·jni·ndk·内存页大小
匆忙拥挤repeat9 小时前
Android Compose 约束布局
android
好安静9 小时前
Android ShellTransitions 机制完整分析(by DeepSeekV4Pro)
android