根据前面文章的分析,InputReader 处理 motion event 时,会对 raw event 进行加工,然后生成高级事件,例如 ACTION_DOWN, ACTION_MOVE, ACTION_UP。本文来分析 InputDispatcher 如何把这些高级事件,分发给窗口。
void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) {
// ...
uint32_t policyFlags = args.policyFlags;
// 来自输入设备的 motion event,都是受信任的
policyFlags |= POLICY_FLAG_TRUSTED;
// 1. 询问分发策略
mPolicy.interceptMotionBeforeQueueing(args.displayId, args.eventTime, policyFlags);
bool needWake = false;
{ // acquire lock
mLock.lock();
if (!(policyFlags & POLICY_FLAG_PASS_TO_USER)) {
}
// 不讨论 input filter 功能
if (shouldSendMotionToInputFilterLocked(args)) {
}
// 2. 加入到 inbound queue
std::unique_ptr<MotionEntry> newEntry =
std::make_unique<MotionEntry>(args.id, args.eventTime, args.deviceId, args.source,
args.displayId, policyFlags, args.action,
args.actionButton, args.flags, args.metaState,
args.buttonState, args.classification, args.edgeFlags,
args.xPrecision, args.yPrecision,
args.xCursorPosition, args.yCursorPosition,
args.downTime, args.pointerCount,
args.pointerProperties, args.pointerCoords);
needWake = enqueueInboundEventLocked(std::move(newEntry));
mLock.unlock();
} // release lock
// 3. 如果有必要,唤醒线程处理事件
if (needWake) {
mLooper->wake();
}
}
InputDispatcher 收到高级的 motion event 后,仍然是先让分发策略处理,然后加入到 inbound queue 中,最后唤醒线程来处理。
分发策略
// com_android_server_input_InputManagerService.cpp
void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, nsecs_t when,
uint32_t& policyFlags) {
ATRACE_CALL();
const bool interactive = mInteractive.load();
if (interactive) {
policyFlags |= POLICY_FLAG_INTERACTIVE;
}
if ((policyFlags & POLICY_FLAG_TRUSTED) == 0 || (policyFlags & POLICY_FLAG_INJECTED)) {
}
// 交互状态下,motion event 不需要经过上层的分发策略处理,直接就允许分发
if (policyFlags & POLICY_FLAG_INTERACTIVE) {
policyFlags |= POLICY_FLAG_PASS_TO_USER;
return;
}
JNIEnv* env = jniEnv();
// 非交互状态下,motion event 是否要分发,还得询问上层分发策略
const jint wmActions =
env->CallIntMethod(mServiceObj,
gServiceClassInfo.interceptMotionBeforeQueueingNonInteractive,
displayId, when, policyFlags);
if (checkAndClearExceptionFromCallback(env, "interceptMotionBeforeQueueingNonInteractive")) {
return;
}
handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
}
void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
uint32_t& policyFlags) {
if (wmActions & WM_ACTION_PASS_TO_USER) {
// 上层分发策略允许分发,policyFlags 中添加 POLICY_FLAG_PASS_TO_USER
policyFlags |= POLICY_FLAG_PASS_TO_USER;
} else {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("handleInterceptActions: Not passing key to user.");
#endif
}
}
在交互状态下,例如亮屏状态,motion event 是不需要经过上层分发策略处理,就直接允许分发。
而如果在非交互状态下,例如灭屏,motion event 由上层的分发策略决定是否分发,如下
// PhoneWindowManager.java
// 注意,这是在非交互状态下,决定是否分发 motion event
public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
int policyFlags) {
if ((policyFlags & FLAG_WAKE) != 0) {
if (wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotion,
PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION")) {
// 如果唤醒屏幕,不分发 motion event
return 0;
}
}
// 在设备处于非交互状态下,只有某些特殊情况,才允许分发 motion event
if (shouldDispatchInputWhenNonInteractive(displayId, KEYCODE_UNKNOWN)) {
return ACTION_PASS_TO_USER;
}
// If we have not passed the action up and we are in theater mode without dreaming,
// there will be no dream to intercept the touch and wake into ambient. The device should
// wake up in this case.
if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) {
wakeUp(whenNanos / 1000000, mAllowTheaterModeWakeFromMotionWhenNotDreaming,
PowerManager.WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
}
// 默认不允许分发 motion event
return 0;
}
private boolean shouldDispatchInputWhenNonInteractive(int displayId, int keyCode) {
// Apply the default display policy to unknown displays as well.
final boolean isDefaultDisplay = displayId == DEFAULT_DISPLAY
|| displayId == INVALID_DISPLAY;
final Display display = isDefaultDisplay
? mDefaultDisplay
: mDisplayManager.getDisplay(displayId);
final boolean displayOff = (display == null
|| display.getState() == STATE_OFF);
// 灭屏,非手表设备,不允许分发 motion event
if (displayOff && !mHasFeatureWatch) {
return false;
}
// 走到这里,表示屏幕处于非灭屏,或者是手表设备
// 设备处于非灭屏,并且有锁屏显示,需要分发 motion event 给锁屏
// Send events to keyguard while the screen is on and it's showing.
if (isKeyguardShowingAndNotOccluded() && !displayOff) {
return true;
}
// Watches handle BACK and hardware buttons specially
if (mHasFeatureWatch && (keyCode == KeyEvent.KEYCODE_BACK
|| keyCode == KeyEvent.KEYCODE_STEM_PRIMARY
|| keyCode == KeyEvent.KEYCODE_STEM_1
|| keyCode == KeyEvent.KEYCODE_STEM_2
|| keyCode == KeyEvent.KEYCODE_STEM_3)) {
return false;
}
// 这里应该指的是 AOD 模式下,是允许分发 motion event 给 AOD 窗口的
if (isDefaultDisplay) {
IDreamManager dreamManager = getDreamManager();
try {
if (dreamManager != null && dreamManager.isDreaming()) {
return true;
}
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException when checking if dreaming", e);
}
}
// 默认,不允许分发 motion event
return false;
}
对于手机来说,设备在处于非交互状态下,并且屏幕处于 OFF 状态下,是不允许分发 motion event 的。
但是,如果屏幕处于非 OFF 状态,例如 doze 状态,如果此时有锁屏窗口 或者 AOD 窗口,那么是允许分发 motion event 的,因为这些窗口,需要处理 motion event。
分发
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LLONG_MAX;
{ // acquire lock
std::scoped_lock _l(mLock);
mDispatcherIsAlive.notify_all();
if (!haveCommandsLocked()) {
// 通过分发循环分发 motion event
dispatchOnceInnerLocked(&nextWakeupTime);
}
// ...
} // release lock
// ...
}
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// ...
if (!mPendingEvent) {
if (mInboundQueue.empty()) {
} else {
// 从 inbound queue 中取出事件
mPendingEvent = mInboundQueue.front();
mInboundQueue.pop_front();
traceInboundQueueLengthLocked();
}
// ...
}
// ...
switch (mPendingEvent->type) {
// ...
case EventEntry::Type::MOTION: {
std::shared_ptr<MotionEntry> motionEntry =
std::static_pointer_cast<MotionEntry>(mPendingEvent);
// ...
// 分发 motion event
done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime);
break;
}
// ...
}
// 成功分发事件
if (done) {
if (dropReason != DropReason::NOT_DROPPED) {
dropInboundEventLocked(*mPendingEvent, dropReason);
}
mLastDropReason = dropReason;
// 重置 mPendingEvent 等等
releasePendingEventLocked();
*nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately
}
}
bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<MotionEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
// ...
// 由丢弃的理由,不分发
// 例如,分发策略不允许分发
if (*dropReason != DropReason::NOT_DROPPED) {
setInjectionResult(*entry,
*dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
: InputEventInjectionResult::FAILED);
return true;
}
const bool isPointerEvent = isFromSource(entry->source, AINPUT_SOURCE_CLASS_POINTER);
// Identify targets.
std::vector<InputTarget> inputTargets;
bool conflictingPointerActions = false;
InputEventInjectionResult injectionResult;
if (isPointerEvent) {
// ...
// 找到手指触摸的窗口
inputTargets =
findTouchedWindowTargetsLocked(currentTime, *entry, &conflictingPointerActions,
/*byref*/ injectionResult);
} else {
}
if (injectionResult == InputEventInjectionResult::PENDING) {
return false;
}
setInjectionResult(*entry, injectionResult);
// ...
// 本文不讨论 monitor channel 处理 motion event
// 例如,返回手势,就是根据这个实现的
addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
// ...
// 通过分发循环,把事件发送给窗口
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
原理很简单,先为 motion event 寻找触摸的窗口,然后通过分发循环,把 motion event 发送给窗口。
分发循环,在前面分析按键事件分发的时候,已经分析过,这里不重复分析。下面重点分析如何为 motion event 找到触摸的窗口。
寻找触摸的窗口
根据前面的分析,InputReader 处理 raw motion event ,会生成 MOTION_DOWN, MOTION_MOVE, MOTION_UP 这样的高级事件。而为这些高级事件,寻找触摸的窗口的原理却不同,因此需要分开来分析
另外,为了简化分析,会省略某些高级功能的代码,例如 split touch。而剩下的代码,就是最基础的原理。一旦掌握了最基本的原理,就可以分析更高级的功能。
为 ACTION_DOWN 寻找触摸的窗口
std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
InputEventInjectionResult& outInjectionResult) {
ATRACE_CALL();
std::vector<InputTarget> targets;
const int32_t displayId = entry.displayId;
const int32_t action = entry.action;
const int32_t maskedAction = MotionEvent::getActionMasked(action);
outInjectionResult = InputEventInjectionResult::PENDING;
const TouchState* oldState = nullptr;
TouchState tempTouchState;
if (const auto it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) {
}
// ...
// false
const bool wasDown = oldState != nullptr && oldState->isDown();
// true
const bool isDown = (maskedAction == AMOTION_EVENT_ACTION_DOWN) ||
(maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN && !wasDown);
// true
const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL ||
maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE;
// ...
if (newGesture) {
tempTouchState.reset();
tempTouchState.deviceId = entry.deviceId;
tempTouchState.source = entry.source;
isSplit = false;
} else if (switchedDevice && maskedAction == AMOTION_EVENT_ACTION_MOVE) {
}
// ...
if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
// 1. 获取 motion event 保存的 x,y 坐标
const auto [x, y] = resolveTouchedPosition(entry);
const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
const bool isStylus = isPointerFromStylus(entry, pointerIndex);
// 2.获取触摸的窗口
auto [newTouchedWindowHandle, outsideTargets] =
findTouchedWindowAtLocked(displayId, x, y, isStylus);
// ...
// 3.获取所有触摸的 spy window,它们也可以作为 motion event 分发的窗口
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 保存了所有触摸的窗口,包括 spy window
newTouchedWindows.insert(newTouchedWindows.begin(), newTouchedWindowHandle);
}
// ...
// 4.遍历所有触摸的窗口
for (const sp<WindowInfoHandle>& windowHandle : newTouchedWindows) {
// 过滤不能处理 motion event 的窗口,例如,窗口没有 input channel
if (!canWindowReceiveMotionLocked(windowHandle, entry)) {
continue;
}
// ...
// Set target flags.
ftl::Flags<InputTarget::Flags> targetFlags = InputTarget::Flags::DISPATCH_AS_IS;
// 如果触摸的窗口是前台窗口,添加标志位
// 可触摸的,并且非 spy window,是前台窗口
if (canReceiveForegroundTouches(*windowHandle->getInfo())) {
// There should only be one touched window that can be "foreground" for the pointer.
targetFlags |= InputTarget::Flags::FOREGROUND;
}
// ..
// 保存手指 id
std::bitset<MAX_POINTER_ID + 1> pointerIds;
if (!isHoverAction) {
pointerIds.set(entry.pointerProperties[pointerIndex].id);
}
// true
const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
// 4.1 保存触摸的窗口到 tempTouchState.windows 集合中
tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds,
isDownOrPointerDown
? std::make_optional(entry.eventTime)
: std::nullopt);
// 如果这个 foreground 窗口,还会显示壁纸,例如 launcher,那么也把壁纸窗口作为输入窗口
if (isDownOrPointerDown) {
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) {
ftl::Flags<InputTarget::Flags> wallpaperFlags =
InputTarget::Flags::WINDOW_IS_OBSCURED |
InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED |
InputTarget::Flags::DISPATCH_AS_IS;
if (isSplit) {
wallpaperFlags |= InputTarget::Flags::SPLIT;
}
tempTouchState.addOrUpdateWindow(wallpaper, wallpaperFlags, pointerIds,
entry.eventTime);
}
}
}
} // 遍历所有 touched window 结束
// ...
} else {
}
// ...
// Output targets from the touch state.
// 现在,tempTouchState.windows 保存的是有效的触摸的窗口
for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) {
continue;
}
// 5.把 empTouchState.windows 保存的窗口,保存到 targets
addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
targets);
}
// ...
outInjectionResult = InputEventInjectionResult::SUCCEEDED;
// ...
if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
if (displayId >= 0) {
tempTouchState.clearWindowsWithoutPointers();
// tempTouchState 保存到 mTouchStatesByDisplay
mTouchStatesByDisplay[displayId] = tempTouchState;
} else {
}
}
// ...
// 6. 返回找到的触摸的窗口
return targets;
}
为 action down 寻找触摸窗口,其实是根据 x,y 坐标寻找触摸的窗口,如下
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.
std::vector<InputTarget> outsideTargets;
const auto& windowHandles = getWindowHandlesLocked(displayId);
for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
// ...
const WindowInfo& info = *windowHandle->getInfo();
// 获取x,y坐标落入的 非spy 窗口
if (!info.isSpy() &&
windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) {
return {windowHandle, outsideTargets};
}
// 本文不讨论 watch outside 窗口的情况
if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
}
}
return {nullptr, {}};
}
bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, float x, float y,
bool isStylus, const ui::Transform& displayTransform) {
const auto inputConfig = windowInfo.inputConfig;
// 窗口必须要可见,才能收到 motion event
if (windowInfo.displayId != displayId ||
inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) {
return false;
}
// 触控笔相关
// false
const bool windowCanInterceptTouch = isStylus && windowInfo.interceptsStylus();
// 窗口必须可触摸
if (inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) && !windowCanInterceptTouch) {
return false;
}
// 判断 x,y 是否落入某个窗口可触摸区域
// Window Manager works in the logical display coordinate space. When it specifies bounds for a
// window as (l, t, r, b), the range of x in [l, r) and y in [t, b) are considered to be inside
// the window. Points on the right and bottom edges should not be inside the window, so we need
// to be careful about performing a hit test when the display is rotated, since the "right" and
// "bottom" of the window will be different in the display (un-rotated) space compared to in the
// logical display in which WM determined the bounds. Perform the hit test in the logical
// display space to ensure these edges are considered correctly in all orientations.
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;
}
找到触摸的窗口后,最终会保存到 tempTouchState ,tempTouchState 最终会保存到 mTouchStatesByDisplay 中。
为 ACTION MOVE 寻找触摸的窗口
std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
InputEventInjectionResult& outInjectionResult) {
// ...
const TouchState* oldState = nullptr;
TouchState tempTouchState;
if (const auto it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) {
oldState = &(it->second);
// tempTouchState 保存了 action down 的触摸的窗口
tempTouchState = *oldState;
}
// ...
// true
const bool wasDown = oldState != nullptr && oldState->isDown();
// false
const bool isDown = (maskedAction == AMOTION_EVENT_ACTION_DOWN) ||
(maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN && !wasDown);
// ...
if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
} else {
// ...
// 处理滑出窗口的情况
if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.pointerCount == 1 &&
tempTouchState.isSlippery()) {
// ...
}
// 本文不讨论多手指的 split touch
if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
}
}
// ...
for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) {
continue;
}
// 把 tempTouchState.windows 中保存的 touched window 保存到 targets
addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
targets);
}
// ...
// 返回找到的触摸窗口
return targets;
}
省略一些特殊的情况,其实 ACTION MOVE 的触摸窗口,仍然是 ACTION DOWN 的触摸窗口。
为 ACTION UP 寻找触摸窗口
std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
nsecs_t currentTime, const MotionEntry& entry, bool* outConflictingPointerActions,
InputEventInjectionResult& outInjectionResult) {
// ...
const TouchState* oldState = nullptr;
TouchState tempTouchState;
if (const auto it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) {
oldState = &(it->second);
// tempTouchState 保存的仍然是 ACTION DOWN 的 touched window
tempTouchState = *oldState;
}
// ...
// true
const bool wasDown = oldState != nullptr && oldState->isDown();
// false
const bool isDown = (maskedAction == AMOTION_EVENT_ACTION_DOWN) ||
(maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN && !wasDown);
// ...
if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
} else {
// ...
// 处理滑出窗口的情况
if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.pointerCount == 1 &&
tempTouchState.isSlippery()) {
// ...
}
// 本文不讨论多手指 split touch
if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
}
}
// ...
// Output targets from the touch state.
for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
if (touchedWindow.pointerIds.none() && !touchedWindow.hasHoveringPointers(entry.deviceId)) {
continue;
}
// 把 tempTouchState.windows 保存的 touched window 保存到 targets
addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
targets);
}
// ...
if (isHoverAction) {
} else if (maskedAction == AMOTION_EVENT_ACTION_UP) {
// Pointer went up.
// 移除手指id对应的 touched window
tempTouchState.removeTouchedPointer(entry.pointerProperties[0].id);
} else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
} else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
} else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
}
// ...
// tempTouchState 此时已经没有 touched window,因此移除 tempTouchState 数据
if (tempTouchState.windows.empty()) {
mTouchStatesByDisplay.erase(displayId);
}
// 返回寻找到的触摸窗口
return targets;
}
过滤一些特殊情况,ACTION UP 的触摸窗口,仍然是 ACTION DOWN 的触摸窗口。
TODO
本文的分析,看起来挺简单的,那是因为我省略了很多细节,主要是为了让我们从整体掌握 motion event 的分发流程。
但是,仍然有很多功能,并没有分析,因为涉及窗口的知识,
- monitor channel 原理,例如,返回手势
- watch outside window
- spy window
- ANR
如果业余空闲时间足够,我会把这些功能逐个分析。