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

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。

整体流程概览

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

  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 直接拦截

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

  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 的事件不会污染后续手势状态。
相关推荐
千里马学框架4 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc4 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077004 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼4 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone4 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen4 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone4 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui