以 音量下键 + 电源键 实现的截屏功能为例,分析组合按键功能的实现原理。
处理音量下键按下事件
当 InputDispatcher 收到音量下键的 key down 事件时,在加入到 inbound queue 之前,首先询问了策略,如下
void InputDispatcher::notifyKey(const NotifyKeyArgs& args) {
// ...
// 1.入队前,询问策略
KeyEvent event;
event.initialize(args.id, args.deviceId, args.source, args.displayId, INVALID_HMAC, args.action,
flags, keyCode, args.scanCode, metaState, repeatCount, args.downTime,
args.eventTime);
mPolicy.interceptKeyBeforeQueueing(event, /*byref*/ policyFlags);
bool needWake = false;
{ // acquire lock
// ...
// 2.加入到 inbound queue
std::unique_ptr<KeyEntry> newEntry =
std::make_unique<KeyEntry>(args.id, args.eventTime, args.deviceId, args.source,
args.displayId, policyFlags, args.action, flags, keyCode,
args.scanCode, metaState, repeatCount, args.downTime);
needWake = enqueueInboundEventLocked(std::move(newEntry));
// ...
} // release lock
// 3.唤醒线程处理事件
if (needWake) {
mLooper->wake();
}
}
策略是由上层 PhoneWindowManager 实现
// PhoneWindowManager.java
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
// ...
final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
// ...
int result;
if (interactive || (isInjected && !isWakeKey)) {
// 1.亮屏情况下,按键事件,默认是需要发送给窗口的
result = ACTION_PASS_TO_USER;
isWakeKey = false;
// ...
} else if (shouldDispatchInputWhenNonInteractive(displayId, keyCode)) {
} else {
}
// ...
final boolean isDefaultDisplayOn = mDefaultDisplayPolicy.isAwake();
// true
final boolean interactiveAndOn = interactive && isDefaultDisplayOn;
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
// 2.处理按键手势
handleKeyGesture(event, interactiveAndOn);
}
// ...
switch (keyCode) {
// ...
// 3. 对音量下键,进行特殊处理
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
if (down) {
// ...
TelecomManager telecomManager = getTelecommService();
if (telecomManager != null && !mHandleVolumeKeysInWM) {
if (telecomManager.isRinging()) {
Log.i(TAG, "interceptKeyBeforeQueueing:"
+ " VOLUME key-down while ringing: Silence ringer!");
telecomManager.silenceRinger();
// 音量下键的 key down 事件,导致来电静音,那么事件不会发送给窗口
result &= ~ACTION_PASS_TO_USER;
break;
}
}
// ...
}
// ...
break;
}
// ...
}
// ...
return result;
}
音量下键的 key down 事件,如果不会执行特殊的功能,例如来电静音,默认是需要发送给窗口的。
组合按键功能,属于按键手势,因此截屏功能是在按键手势流程中实现的
// PhoneWindowManager.java
private void handleKeyGesture(KeyEvent event, boolean interactive) {
// 此时单个按键,不会截断
if (mKeyCombinationManager.interceptKey(event, interactive)) {
}
// ...
}
组合键功能,是由 KeyCombinationManager 管理的
// KeyCombinationManager.java
boolean interceptKey(KeyEvent event, boolean interactive) {
synchronized (mLock) {
return interceptKeyLocked(event, interactive);
}
}
private boolean interceptKeyLocked(KeyEvent event, boolean interactive) {
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final int keyCode = event.getKeyCode();
final int count = mActiveRules.size();
final long eventTime = event.getEventTime();
if (interactive && down) {
if (mDownTimes.size() > 0) {
}
if (mDownTimes.get(keyCode) == 0) {
// 1. 保存音量下键按下的时间
mDownTimes.put(keyCode, eventTime);
} else {
}
if (mDownTimes.size() == 1) {
mTriggeredRule = null;
// 2. 匹配组合键规则,保存到 mActiveRules
forAllRules(mRules, (rule)-> {
if (rule.shouldInterceptKey(keyCode)) {
mActiveRules.add(rule);
}
});
} else {
}
} else {
}
return false;
}
由于只是组合键的第一个按键按下,因此 KeyCombinationManager 只为音量下键的 key down 事件,保存了按下时间点,以及匹配了组合键规则。
PhoneWindowManager 对 KeyCombinationManager 初始化时,填充了各种规则,如下
// PhoneWindowManager.java
private void initKeyCombinationRules() {
mKeyCombinationManager = new KeyCombinationManager(mHandler);
final boolean screenshotChordEnabled = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_enableScreenshotChord);
if (screenshotChordEnabled) {
mKeyCombinationManager.addRule(
new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) {
@Override
void execute() {
mPowerKeyHandled = true;
interceptScreenshotChord(
SCREENSHOT_KEY_CHORD, getScreenshotChordLongPressDelay());
}
@Override
void cancel() {
cancelPendingScreenshotChordAction();
}
});
// ...
}
// ... 省略其他组合键规则 ....
}
OK,至此,音量下键的 key down 事件,在加入到 InputDispatcher 之前的策略处理,已经分析完了,有两个效果
- 除了特殊情况以外,一般来说,音量下键的 key down 事件,是允许分发给窗口的。
- KeyCombinationManager 为音量下键匹配了一个截屏规则。
音量下键的 key down 事件的处理,现在转入到底层,InputDispatcher 会把它加入到 inbound queue,然后通过线程循环,处理事件,如下
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LLONG_MAX;
{ // acquire lock
std::scoped_lock _l(mLock);
mDispatcherIsAlive.notify_all();
// Run a dispatch loop if there are no pending commands.
// The dispatch loop might enqueue commands to run afterwards.
// 1. 如果没有命令需要执行,那么先执行一次分发循环
if (!haveCommandsLocked()) {
// 分发循环,也会影响线程的下次唤醒时间,这个时间保存到 nextWakeupTime
dispatchOnceInnerLocked(&nextWakeupTime);
}
// Run all pending commands if there are any.
// If any commands were run then force the next poll to wake up immediately.
// 2. 执行命令
if (runCommandsLockedInterruptable()) {
// 如果有命令成功执行,那么立即开始下一次线程循环
nextWakeupTime = LLONG_MIN;
}
// 3. 计算线程的下次唤醒时间
// If we are still waiting for ack on some events,
// we might have to wake up earlier to check if an app is anr'ing.
const nsecs_t nextAnrCheck = processAnrsLocked();
nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
// We are about to enter an infinitely long sleep, because we have no commands or
// pending or queued events
if (nextWakeupTime == LLONG_MAX) {
mDispatcherEnteredIdle.notify_all();
}
} // release lock
// Wait for callback or timeout or wake. (make sure we round up, not down)
// 4. 线程休眠指定时间后自动唤醒
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
一次 InputDispatcher 的线程循环,主要做了三件事
- 执行分发循环。
- 执行命令。
- 线程休眠指定时间后唤醒。
首先看分发循环,它会对音量下键的 key down event 进行分发
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// ...
if (!mPendingEvent) {
if (mInboundQueue.empty()) {
} else {
// 1. 从 inbound queue 取出事件
mPendingEvent = mInboundQueue.front();
mInboundQueue.pop_front();
traceInboundQueueLengthLocked();
}
// ...
}
// ...
switch (mPendingEvent->type) {
// ...
case EventEntry::Type::KEY: {
std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);
// ...
// 2.分发按键事件
done = dispatchKeyLocked(currentTime, keyEntry, &dropReason, nextWakeupTime);
break;
}
// ...
}
// 音量下键,此次没有成功分发给窗口,done 为 false
if (done) {
// ...
}
}
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
// ...
if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
}
// 此时走这里
if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::UNKNOWN) {
if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) { // 入队前的策略,允许音量下键 key down event 分发
// 获取焦点窗口
sp<IBinder> focusedWindowToken =
mFocusResolver.getFocusedWindowToken(getTargetDisplayId(*entry));
// 创建一个命令,保存到 mCommandQueue
auto command = [this, focusedWindowToken, entry]() REQUIRES(mLock) {
// 执行命令,就是执行这个函数
doInterceptKeyBeforeDispatchingCommand(focusedWindowToken, *entry);
};
postCommandLocked(std::move(command));
// Poke user activity for keys not passed to user
pokeUserActivityLocked(*entry);
// 返回 false,表示此次分发循环,没有处理掉事件
return false; // wait for the command to run
} else {
// ...
}
} else if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::SKIP) {
}
// ...
}
此次分发循环,处理 音量下键的 key down event,仅仅创建并保存了一个命令,然后就此结束了。
根据前面分析,线程循环在执行了分发循环后,就开始执行命令,来看下为音量下键的 key down event 创建的命令,做了什么
void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
KeyEntry& entry) {
const KeyEvent event = createKeyEvent(entry);
nsecs_t delay = 0;
{
scoped_unlock unlock(mLock);
// 1.分发前,询问策略,看看策略是否允许截断
delay = mPolicy.interceptKeyBeforeDispatching(focusedWindowToken, event, entry.policyFlags);
}
// 2.处理策略返回的结果
if (delay < 0) {
entry.interceptKeyResult = KeyEntry::InterceptKeyResult::SKIP;
} else if (delay == 0) {
entry.interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE;
} else {
entry.interceptKeyResult = KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER;
entry.interceptKeyWakeupTime = now() + delay;
}
}
原来,为音量下键的 key down event 创建的命令,其实就是在分发前,询问策略是否截断。
先来看下策略如何处理,后面再看 InputDispatcher 处理策略返回的结果
// PhoneWindowManager.java
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
int policyFlags) {
final boolean keyguardOn = keyguardOn();
final int keyCode = event.getKeyCode();
final int repeatCount = event.getRepeatCount();
final int metaState = event.getMetaState();
final int flags = event.getFlags();
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final boolean canceled = event.isCanceled();
final int displayId = event.getDisplayId();
final long key_consumed = -1;
final long key_not_consumed = 0;
if (DEBUG_INPUT) {
Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
+ repeatCount + " keyguardOn=" + keyguardOn + " canceled=" + canceled);
}
// 首先询问 KeyCombinationManager 是否消费此按键事件
// 由于此时,只有组合键的一个按键按下,因此不会消费此按键事件
if (mKeyCombinationManager.isKeyConsumed(event)) {
}
if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
final long now = SystemClock.uptimeMillis();
// 获取组合键功能的超时时间
final long interceptTimeout = mKeyCombinationManager.getKeyInterceptTimeout(keyCode);
if (now < interceptTimeout) {
// 返回的时间段
return interceptTimeout - now;
}
}
// ...
}
long getKeyInterceptTimeout(int keyCode) {
synchronized (mLock) {
if (mDownTimes.get(keyCode) == 0) {
return 0;
}
long delayMs = 0;
// 从已匹配的规则中,获取组合键的超时时间
for (final TwoKeysCombinationRule rule : mActiveRules) {
if (rule.shouldInterceptKey(keyCode)) {
delayMs = Math.max(delayMs, rule.getKeyInterceptDelayMs());
}
}
// Make sure the delay is less than COMBINE_KEY_DELAY_MILLIS.
delayMs = Math.min(delayMs, COMBINE_KEY_DELAY_MILLIS);
return mDownTimes.get(keyCode) + delayMs;
}
}
对于截屏的组合按键功能,由于此时只有音量下键按下,因此 KeyCombinationManager 不会消费这个按键事件,并且根据已经匹配的规则,返回最小的超时时间。
然后,策略返回给底层的是一个超时的 duration,并不是时间点,来看下底层的处理
void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
KeyEntry& entry) {
const KeyEvent event = createKeyEvent(entry);
nsecs_t delay = 0;
{
scoped_unlock unlock(mLock);
// 1.分发前,询问策略,看看策略是否允许截断
delay = mPolicy.interceptKeyBeforeDispatching(focusedWindowToken, event, entry.policyFlags);
}
// 2.处理策略返回的结果
if (delay < 0) {
} else if (delay == 0) {
} else {
entry.interceptKeyResult = KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER;
entry.interceptKeyWakeupTime = now() + delay;
}
}
策略处理的结果,是返回一个时间段,值大于0,因此,KeyEvent::interceptKeyResult 保存了此次策略的截断结果为 TRY_AGAIN_LATER,KeyEvent::interceptKeyWakeupTime 保存了下次处理此事件的时间点。
OK,现在又回到线程循环,看看下一步如何执行
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LLONG_MAX;
{ // acquire lock
std::scoped_lock _l(mLock);
mDispatcherIsAlive.notify_all();
// Run a dispatch loop if there are no pending commands.
// The dispatch loop might enqueue commands to run afterwards.
// 1. 如果没有命令需要执行,那么先执行一次分发循环
if (!haveCommandsLocked()) {
// 分发循环,也会影响线程的下次唤醒时间,这个时间保存到 nextWakeupTime
dispatchOnceInnerLocked(&nextWakeupTime);
}
// Run all pending commands if there are any.
// If any commands were run then force the next poll to wake up immediately.
// 2. 执行命令
if (runCommandsLockedInterruptable()) {
// 如果有命令成功执行,那么立即开始下一次线程循环
nextWakeupTime = LLONG_MIN;
}
// ...
} // release lock
// Wait for callback or timeout or wake. (make sure we round up, not down)
// 3. 线程休眠指定时间后自动唤醒
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
由于第二步已经有命令执行了,那么线程下次唤醒时间点设置为了 LLONG_MIN,因此得立即开始第二次线程循环。
OK,那么继续看线程循环的第一步,分发循环。由于前一次并没有成功分发音量下键的 key down event,因此,再次来到分发按键事件的地方
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
// ...
// 音量下键的截断结果,此时为 TRY_AGAIN_LATER
if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
// 当前时间,小于下次处理事件的的超时事件
if (currentTime < entry->interceptKeyWakeupTime) {
// 计算线程下次唤醒时间点,保存到 nextWakeupTime
if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
*nextWakeupTime = entry->interceptKeyWakeupTime;
}
// 返回 false,表示此时又没有处理掉音量下键的 key down event
return false; // wait until next wakeup
}
// ...
}
// ...
}
此次分发循环,又没有处理掉音量下键的 key down event,而只是计算了线程的下次唤醒时间点。
那么,再此回到线程循环
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LLONG_MAX;
{ // acquire lock
std::scoped_lock _l(mLock);
mDispatcherIsAlive.notify_all();
// Run a dispatch loop if there are no pending commands.
// The dispatch loop might enqueue commands to run afterwards.
// 1. 如果没有命令需要执行,那么先执行一次分发循环
if (!haveCommandsLocked()) {
// 分发循环,也会影响线程的下次唤醒时间,这个时间保存到 nextWakeupTime
dispatchOnceInnerLocked(&nextWakeupTime);
}
// Run all pending commands if there are any.
// If any commands were run then force the next poll to wake up immediately.
// 2. 执行命令
if (runCommandsLockedInterruptable()) {
// 如果有命令成功执行,那么立即开始下一次线程循环
nextWakeupTime = LLONG_MIN;
}
// ...
} // release lock
// Wait for callback or timeout or wake. (make sure we round up, not down)
// 3. 线程休眠指定时间后自动唤醒
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
分发循环计算出了线程下次唤醒时间点,然后又没有命令需要执行,因此,线程会进入休眠。
处理电源键按下事件
在 InputDispatcher 线程休眠的时间内,如果按下组合键的第二个按键,也就是电源键,那么就会触发截屏功能。
同样,在 power key down event 加入到 InputDispatche 的 inbound queue 之前,首先还是得询问策略
// PhoneWindowManager.java
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
// ...
final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
// ...
final boolean isDefaultDisplayOn = mDefaultDisplayPolicy.isAwake();
final boolean interactiveAndOn = interactive && isDefaultDisplayOn;
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
// 处理按键手势
handleKeyGesture(event, interactiveAndOn);
}
// ...
switch (keyCode) {
// ...
case KeyEvent.KEYCODE_POWER: {
EventLogTags.writeInterceptPower(
KeyEvent.actionToString(event.getAction()),
mPowerKeyHandled ? 1 : 0,
mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
// 电源按键事件,不需要分发给窗口
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down) {
interceptPowerKeyDown(event, interactiveAndOn);
} else {
interceptPowerKeyUp(event, canceled);
}
break;
}
// ...
}
// ...
return result;
}
private void handleKeyGesture(KeyEvent event, boolean interactive) {
// 此时会触发组合按键功能,因此会截断电源键 key down event
if (mKeyCombinationManager.interceptKey(event, interactive)) {
// handled by combo keys manager.
mSingleKeyGestureDetector.reset();
return;
}
// ...
}
在按键手势的处理中,由于 KeyCombinationManager 收到截屏组合键的第二个按键事件,因此会触发截屏。同时,我们还要注意,策略处理电源键的 key down event ,不允许它分发给窗口。
来看下组合键功能的触发流程
// KeyCombinationManager.java
boolean interceptKey(KeyEvent event, boolean interactive) {
synchronized (mLock) {
return interceptKeyLocked(event, interactive);
}
}
private boolean interceptKeyLocked(KeyEvent event, boolean interactive) {
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final int keyCode = event.getKeyCode();
final int count = mActiveRules.size();
final long eventTime = event.getEventTime();
if (interactive && down) {
if (mDownTimes.size() > 0) {
// 第二个按键没有超时
if (count > 0
&& eventTime > mDownTimes.valueAt(0) + COMBINE_KEY_DELAY_MILLIS) {
} else if (count == 0) {
}
}
if (mDownTimes.get(keyCode) == 0) {
mDownTimes.put(keyCode, eventTime);
} else {
}
if (mDownTimes.size() == 1) {
} else {
// Ignore if rule already triggered.
if (mTriggeredRule != null) {
return true;
}
// check if second key can trigger rule, or remove the non-match rule.
forAllActiveRules((rule) -> {
if (!rule.shouldInterceptKeys(mDownTimes)) {
return false;
}
Log.v(TAG, "Performing combination rule : " + rule);
// 执行组合键的规则
mHandler.post(rule::execute);
// 保存已触发的规则
mTriggeredRule = rule;
// 只触发第一个规则
return true;
});
// mActiveRules 只保存已出发的规则
mActiveRules.clear();
if (mTriggeredRule != null) {
mActiveRules.add(mTriggeredRule);
return true;
}
}
} else {
}
return false;
}
很简单,就是找到对应的规则,执行它。
现在,策略处理电源键的 key down event 时,触发了截屏功能,并且不允许分发这个事件。那么,InputDispatcher 线程就会被唤醒来处理事件了,来看下线程循环的分发循环
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// ...
// mPendingEvent 此时仍然保存着为处理的音量下键的 key down event
if (!mPendingEvent) {
}
// ...
switch (mPendingEvent->type) {
// ...
case EventEntry::Type::KEY: {
std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);
// ...
// 继续分发音量下键的 key down event
done = dispatchKeyLocked(currentTime, keyEntry, &dropReason, nextWakeupTime);
break;
}
// ...
}
// 分发结果,仍然是 false
if (done) {
}
}
很不幸,此次线程循环的分发循环,并不能立即分发电源键的 key down event,因为组合键的第一个按键事件,也就是音量下键的 key down event 还没有处理完,因此,还得继续分发它
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
// ...
if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
// 由于是在超时时间内,按下的第二个按键,因此,现在的时间,仍然小于音量下键的超时唤醒处理时间
if (currentTime < entry->interceptKeyWakeupTime) {
// 根据现在的事件,计算线程下次唤醒时间
if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
*nextWakeupTime = entry->interceptKeyWakeupTime;
}
// 线程得继续休眠
return false; // wait until next wakeup
}
// ...
}
// ...
}
由于在超时时间内,按下了电源键,因此,很不幸,此次分发循环,又没有处理掉音量下键的 key down event,而是继续让线程休眠到指定时间点。
OK,当线程休眠指定时间,自动唤醒后,再次开始新一轮的线程循环,这次分发循环又得继续处理音量下键的 key down event
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
// ...
if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
// 此次当前时间,肯定超过这个超时时间
if (currentTime < entry->interceptKeyWakeupTime) {
}
// 重置截断结果以及时间
entry->interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN;
entry->interceptKeyWakeupTime = 0;
}
if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::UNKNOWN) {
if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
sp<IBinder> focusedWindowToken =
mFocusResolver.getFocusedWindowToken(getTargetDisplayId(*entry));
// 添加命令,询问策略
auto command = [this, focusedWindowToken, entry]() REQUIRES(mLock) {
doInterceptKeyBeforeDispatchingCommand(focusedWindowToken, *entry);
};
postCommandLocked(std::move(command));
// Poke user activity for keys not passed to user
pokeUserActivityLocked(*entry);
// 结束此次分发循环
return false; // wait for the command to run
} else {
}
} else if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::SKIP) {
}
// ...
}
oh shit,此次分发询问,还是没有处理掉音量下键的 key down event,又是添加命令后,草草结束了此次分发循环。
那么,接着就来到了线程循环的执行命令,也就是询问策略如何处理音量下键的 key down event
// PhoneWindowManager.java
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
int policyFlags) {
// ...
final long key_consumed = -1;
if (mKeyCombinationManager.isKeyConsumed(event)) {
return key_consumed;
}
// ...
}
// KeyCombinationManager.java
boolean isKeyConsumed(KeyEvent event) {
synchronized (mLock) {
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) {
return false;
}
// mTriggeredRule 刚才保存了已经出发的规则
return mTriggeredRule != null && mTriggeredRule.shouldInterceptKey(event.getKeyCode());
}
}
由于已经出发了组合键功能,也就是截屏功能,因此,策略此次处理音量下键的 key down event 的结果是,返回 -1。
那么,接着看下底层如何处理
void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
KeyEntry& entry) {
const KeyEvent event = createKeyEvent(entry);
nsecs_t delay = 0;
{ // release lock
// 策略处理音量下键,返回 -1
delay = mPolicy.interceptKeyBeforeDispatching(focusedWindowToken, event, entry.policyFlags);
} // acquire lock
if (delay < 0) {
// 保存策略处理结果为 SKIP
entry.interceptKeyResult = KeyEntry::InterceptKeyResult::SKIP;
} else if (delay == 0) {
} else {
}
}
OK,此次线程循环的执行命令就结束,由于有命令成功执行,因此会立即执行下一次线程循环,而这次线程循环的分发询问,仍然继续分发音量下键的 key down event
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// ...
// mPendingEvent 此时保存的仍然是音量下键的 key down event
if (!mPendingEvent) {
}
// ...
switch (mPendingEvent->type) {
// ...
case EventEntry::Type::KEY: {
std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);
// ...
// 分发音量下键
done = dispatchKeyLocked(currentTime, keyEntry, &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::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
// ...
if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
}
if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::UNKNOWN) {
} else if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::SKIP) {
// 保存丢弃结果
if (*dropReason == DropReason::NOT_DROPPED) {
*dropReason = DropReason::POLICY;
}
}
// 丢弃事件
if (*dropReason != DropReason::NOT_DROPPED) {
setInjectionResult(*entry,
*dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
: InputEventInjectionResult::FAILED);
mReporter->reportDroppedKey(entry->id);
// Poke user activity for undispatched keys
pokeUserActivityLocked(*entry);
// 代表成功处理了音量下键
return true;
}
// ...
}
山重水复疑无路,柳暗花明又一村!终于,分发循环处理掉了音量下键的 key down event,丢弃它! 因为它触发了截屏功能,丢失它,很合理! 然后,分发循环会重置 mPendingEvent 为 null,重置 nextWakeupTime 为 LLONG_MIN,表示需要立即开始下一次线程循环。
OK,再次开始新一轮的线程循环,此次分发的就是被阻塞的电源键的 key down event。根据前面的分析,在入队前,策略处理的结果是不允许分发,那么此次线程循环的分发循环,就会丢弃这个事件,如下
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// ...
if (!mPendingEvent) {
if (mInboundQueue.empty()) {
// ...
} else {
// 取出事件,也就是电源键事件
mPendingEvent = mInboundQueue.front();
mInboundQueue.pop_front();
traceInboundQueueLengthLocked();
}
// ...
}
bool done = false;
DropReason dropReason = DropReason::NOT_DROPPED;
if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
// 被策略丢弃
dropReason = DropReason::POLICY;
} else if (!mDispatchEnabled) {
}
// ...
switch (mPendingEvent->type) {
// ...
case EventEntry::Type::KEY: {
std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);
// ...
// 分发按键
done = dispatchKeyLocked(currentTime, keyEntry, &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::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
// ....
if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::UNKNOWN) {
if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
} else {
// 入队前的策略,处理电源键的结果是,不允许分发,因此走这里
entry->interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE;
}
} else if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::SKIP) {
}
// dropReason 此时为 DropReason::POLICY
// 电源键事件被丢弃
if (*dropReason != DropReason::NOT_DROPPED) {
setInjectionResult(*entry,
*dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
: InputEventInjectionResult::FAILED);
mReporter->reportDroppedKey(entry->id);
// Poke user activity for undispatched keys
pokeUserActivityLocked(*entry);
// 返回 true,代表电源键被处理,即丢弃
return true;
}
// ...
}
至此,截屏组合键的功能,就分析完毕了。
想法
我曾经质疑过,也有人对我质疑过,搞清楚这些基础流程有用吗? 虽然时间的推移,这些质疑就变得很肤浅,须知,经济基础决定上层建筑!