按键事件,在加入到 InputDispatcher 的 inbound queue 之前,会先询问策略是否截断,这个策略是由上层的 PhoneWindowManager 实现的。
一般来说,如果按键事件触发了单按键手势,例如,单击、长按,那么策略就一定会截断这个按键事件。否则,不截断。
本文就来分析下,策略是如何实现单按键手势。
单按键手势
策略是由上层的 PhoneWindowManager 实现的,当没有入队的按键事件到来时,它会处理按键手势功能,如下
// 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);
}
// ...
}
private void handleKeyGesture(KeyEvent event, boolean interactive) {
// 处理组合按键手势
if (mKeyCombinationManager.interceptKey(event, interactive)) {
mSingleKeyGestureDetector.reset();
return;
}
// ...
// 处理单按键手势
mSingleKeyGestureDetector.interceptKey(event, interactive);
}
按键手势分为组合按键手势和单按键手势,本文分析单按键手势,下一篇文章分析组合按键手势。
// SingleKeyGestureDetector.java
void interceptKey(KeyEvent event, boolean interactive) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
// Store the non interactive state when first down.
if (mDownKeyCode == KeyEvent.KEYCODE_UNKNOWN || mDownKeyCode != event.getKeyCode()) {
mBeganFromNonInteractive = !interactive;
}
interceptKeyDown(event);
} else {
interceptKeyUp(event);
}
}
SingleKeyGestureDetector 分别对按键的 key down event 和 key up event 进行处理,从而实现单按键手势。下面分为三部分,分别看看按键的单击、多击、长按手势是如何实现的。
单击手势
按键按下,然后抬起,这是一次单击手势。首先看下按键按下时的 key down event 是如何处理的,如下
// SingleKeyGestureDetector.java
private void interceptKeyDown(KeyEvent event) {
final int keyCode = event.getKeyCode();
// ...
// 1.保存按键按下的 key code
mDownKeyCode = keyCode;
// 2.根据 key code,获取一个有效的规则
if (mActiveRule == null) {
final int count = mRules.size();
for (int index = 0; index < count; index++) {
final SingleKeyRule rule = mRules.get(index);
if (rule.shouldInterceptKey(keyCode)) {
if (DEBUG) {
Log.i(TAG, "Intercept key by rule " + rule);
}
mActiveRule = rule;
break;
}
}
mLastDownTime = 0;
}
if (mActiveRule == null) {
return;
}
// 这里是检测按键被按下多少次
final long keyDownInterval = event.getDownTime() - mLastDownTime;
mLastDownTime = event.getDownTime();
if (keyDownInterval >= MULTI_PRESS_TIMEOUT) {
mKeyPressCounter = 1;
} else {
// ...
}
if (mKeyPressCounter == 1) { // 按键首次按下
// 处理规则支持按键长按功能
if (mActiveRule.supportLongPress()) {
// ...
}
// 处理规则支持非常时间长按功能
if (mActiveRule.supportVeryLongPress()) {
// ...
}
} else {
// ...
}
}
当按下按键时,SingleKeyGestureDetector 保存了按键的 key code,然后根据 key code 匹配一个规则,如下
// SingleKeyGestureDetector.java
abstract static class SingleKeyRule {
private boolean shouldInterceptKey(int keyCode) {
return keyCode == mKeyCode;
}
}
再来看下按键抬起时的 key up event 是如何处理的,如下
// SingleKeyGestureDetector.java
private boolean interceptKeyUp(KeyEvent event) {
// 重置 mDownKeyCode
mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
if (mActiveRule == null) {
return false;
}
// ...
if (event.getKeyCode() == mActiveRule.mKeyCode) {
// 1. Directly trigger short press when max count is 1.
if (mActiveRule.getMaxMultiPressCount() == 1) {
if (DEBUG) {
Log.i(TAG, "press key " + KeyEvent.keyCodeToString(event.getKeyCode()));
}
Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
1, mActiveRule);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
mActiveRule = null;
return true;
}
// 2. This could be a multi-press. Wait a little bit longer to confirm.
if (mKeyPressCounter < mActiveRule.getMaxMultiPressCount()) {
Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
mKeyPressCounter, mActiveRule);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT);
}
return true;
}
// ...
}
当按键抬起时,如果规则只支持单击,那么立即触发单击功能。而如果规则支持多击,那么需要发送一个延时消息来确认是触发单击还是要触发多击。如果在超时的时间内,按键没有再次按下,那么表示触发的就是单击功能,否则要触发多击功能。
处理单击功能的消息,其实就是让规则执行单击动作,如下
// SingleKeyGestureDetector.java
private class KeyHandler extends Handler {
// ...
@Override
public void handleMessage(Message msg) {
final SingleKeyRule rule = (SingleKeyRule) msg.obj;
final int keyCode = msg.arg1;
final int pressCount = msg.arg2;
switch(msg.what) {
// ...
case MSG_KEY_DELAYED_PRESS:
if (DEBUG) {
Log.i(TAG, "Detect press " + KeyEvent.keyCodeToString(keyCode)
+ ", count " + pressCount);
}
if (pressCount == 1) {
// 让规则执行单击功能
rule.onPress(mLastDownTime);
} else {
// ...
}
break;
}
}
}
多击手势
多击手势,以双击为例进行分析。
根据单击手势的分析,在按键抬起时,如果在一个超时时间内,按键再次按下,那么会触发多击功能,如下
// SingleKeyGestureDetector.java
private boolean interceptKeyUp(KeyEvent event) {
// 重置 mDownKeyCode
mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
// ...
if (event.getKeyCode() == mActiveRule.mKeyCode) {
// ...
// This could be a multi-press. Wait a little bit longer to confirm.
if (mKeyPressCounter < mActiveRule.getMaxMultiPressCount()) {
Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
mKeyPressCounter, mActiveRule);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT);
}
return true;
}
// ...
}
假如在超时时间内,按键再次按下,就要移除单击手势的超时消息,避免触发单击手势,如下
// SingleKeyGestureDetector.java
private void interceptKeyDown(KeyEvent event) {
final int keyCode = event.getKeyCode();
// ...
mDownKeyCode = keyCode;
// ...
final long keyDownInterval = event.getDownTime() - mLastDownTime;
mLastDownTime = event.getDownTime();
// 由于是在超时时间内再次按下按键,因此 keyDownInterval 要小于 MULTI_PRESS_TIMEOUT
if (keyDownInterval >= MULTI_PRESS_TIMEOUT) {
// ...
} else {
// 记录按键按下的次数
mKeyPressCounter++;
}
if (mKeyPressCounter == 1) {
// ...
} else {
// ...
// 此时要触发多击,因此移除单击的消息
mHandler.removeMessages(MSG_KEY_DELAYED_PRESS);
// 如果达到规则最大的多击次数,那么直接触发多击功能
// Trigger multi press immediately when reach max count.( > 1)
if (mActiveRule.getMaxMultiPressCount() > 1
&& mKeyPressCounter == mActiveRule.getMaxMultiPressCount()) {
if (DEBUG) {
Log.i(TAG, "Trigger multi press " + mActiveRule.toString() + " for it"
+ " reached the max count " + mKeyPressCounter);
}
final Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, keyCode,
mKeyPressCounter, mActiveRule);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
}
}
当按键再次抬起时,就需要触发双击,如下
// SingleKeyGestureDetector.java
private boolean interceptKeyUp(KeyEvent event) {
// 重置 mDownKeyCode
mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
// ...
if (event.getKeyCode() == mActiveRule.mKeyCode) {
// ...
// This could be a multi-press. Wait a little bit longer to confirm.
if (mKeyPressCounter < mActiveRule.getMaxMultiPressCount()) {
Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
mKeyPressCounter, mActiveRule);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT);
}
return true;
}
// ...
}
如果按键按下的次数,没有达到规则的最大点击次数,那么需要发送一个延时消息,来确认是否执行多击。
这里假设在超时时间内,按键没有第三次按下,那么就会触发双击功能,其实就是触发规则的多击功能,如下
// SingleKeyGestureDetector.java
private class KeyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
final SingleKeyRule rule = (SingleKeyRule) msg.obj;
final int keyCode = msg.arg1;
final int pressCount = msg.arg2;
switch(msg.what) {
// ...
case MSG_KEY_DELAYED_PRESS:
if (DEBUG) {
Log.i(TAG, "Detect press " + KeyEvent.keyCodeToString(keyCode)
+ ", count " + pressCount);
}
if (pressCount == 1) {
// ...
} else {
// 触发规则的多击功能
rule.onMultiPress(mLastDownTime, pressCount);
}
break;
}
}
}
长按手势
长按手势,根据底层是否支持按键长按功能,有两种不同实现。
如果底层支持按键长按功能,那么当按键首次按下,并且一定时间内不抬起时,底层会再次上报一次 key down event,InputDispatcher 会认为是一次长按事件,如下
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
if (!entry->dispatchInProgress) {
if (entry->repeatCount == 0 && entry->action == AKEY_EVENT_ACTION_DOWN &&
(entry->policyFlags & POLICY_FLAG_TRUSTED) &&
(!(entry->policyFlags & POLICY_FLAG_DISABLE_KEY_REPEAT))) {
if (mKeyRepeatState.lastKeyEntry &&
mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode &&
mKeyRepeatState.lastKeyEntry->deviceId == entry->deviceId) {
// 2. 如果再次收到 key down event,那么entry->repeatCount 记录按键重复按下的次数
entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
resetKeyRepeatLocked();
mKeyRepeatState.nextRepeatTime = LLONG_MAX; // don't generate repeats ourselves
} else {
// Not a repeat. Save key down state in case we do see a repeat later.
resetKeyRepeatLocked();
mKeyRepeatState.nextRepeatTime = entry->eventTime + mConfig.keyRepeatTimeout;
}
// 1. 按键首次按下时,保存 KetEvent
mKeyRepeatState.lastKeyEntry = entry;
} else if (entry->action == AKEY_EVENT_ACTION_UP && mKeyRepeatState.lastKeyEntry &&
mKeyRepeatState.lastKeyEntry->deviceId != entry->deviceId) {
// ...
} else if (!entry->syntheticRepeat) {
// ..
}
// 3. 只在 repeat count 为 1 时,在 KeyEvent::flgas 中添加 AKEY_EVENT_FLAG_LONG_PRESS
if (entry->repeatCount == 1) {
entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS;
} else {
entry->flags &= ~AKEY_EVENT_FLAG_LONG_PRESS;
}
// ...
}
// ...
}
当 SingleKeyGestureDetector 检测到带有 AKEY_EVENT_FLAG_LONG_PRESS 标志位的 key down event 时,就会触发长按手势功能,如下
// SingleKeyGestureDetector.java
private void interceptKeyDown(KeyEvent event) {
final int keyCode = event.getKeyCode();
// same key down.
if (mDownKeyCode == keyCode) {
if (mActiveRule != null && (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0
&& mActiveRule.supportLongPress() && !mHandledByLongPress) {
if (DEBUG) {
Log.i(TAG, "Long press key " + KeyEvent.keyCodeToString(keyCode));
}
mHandledByLongPress = true;
mHandler.removeMessages(MSG_KEY_LONG_PRESS);
mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0,
mActiveRule);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
return;
}
// ...
}
而如果底层不支持按键长按功能,当按键首次按下时,SingleKeyGestureDetector 会通过一个延时消息来确认是否触发长按,如下
// SingleKeyGestureDetector.java
private void interceptKeyDown(KeyEvent event) {
final int keyCode = event.getKeyCode();
// ...
mDownKeyCode = keyCode;
// 匹配一个规则
if (mActiveRule == null) {
final int count = mRules.size();
for (int index = 0; index < count; index++) {
final SingleKeyRule rule = mRules.get(index);
if (rule.shouldInterceptKey(keyCode)) {
if (DEBUG) {
Log.i(TAG, "Intercept key by rule " + rule);
}
mActiveRule = rule;
break;
}
}
mLastDownTime = 0;
}
if (mActiveRule == null) {
return;
}
final long keyDownInterval = event.getDownTime() - mLastDownTime;
mLastDownTime = event.getDownTime();
if (keyDownInterval >= MULTI_PRESS_TIMEOUT) {
mKeyPressCounter = 1;
} else {
// ...
}
if (mKeyPressCounter == 1) {
// 如果规则支持长按,发送一个延时消息,来确认是否触发长按
if (mActiveRule.supportLongPress()) {
final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0,
mActiveRule);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg, mActiveRule.getLongPressTimeoutMs());
}
// ...
} else {
// ...
}
}
如果在超时时间内,按键没有抬起,那么会执行消息,即触发规则的按键长按功能,如下
// SingleKeyGestureDetector.java
private class KeyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
final SingleKeyRule rule = (SingleKeyRule) msg.obj;
if (rule == null) {
Log.wtf(TAG, "No active rule.");
return;
}
final int keyCode = msg.arg1;
final int pressCount = msg.arg2;
switch(msg.what) {
case MSG_KEY_LONG_PRESS:
if (DEBUG) {
Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode));
}
rule.onLongPress(mLastDownTime);
break;
// ...
}
}
}
电源键手势
系统为电源键添加一个单按键手势规则,如下
// PhoneWindowManager.java
private void initSingleKeyGestureRules() {
mSingleKeyGestureDetector = SingleKeyGestureDetector.get(mContext);
mSingleKeyGestureDetector.addRule(new PowerKeyRule());
// ...
}
看看它的单击功能是如何实现的
// PhoneWindowManager.java
private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
// ...
@Override
void onPress(long downTime) {
powerPress(downTime, 1 /*count*/,
mSingleKeyGestureDetector.beganFromNonInteractive());
}
// ...
}
private void powerPress(long eventTime, int count, boolean beganFromNonInteractive) {
// ....
// 这个变量命名错误,其实它代表屏幕是否处理亮屏状态
final boolean interactive = mDefaultDisplayPolicy.isAwake();
// 触发电源键单击功能的log
// 注意,这个log在实际工作中有很大用处
Slog.d(TAG, "powerPress: eventTime=" + eventTime + " interactive=" + interactive
+ " count=" + count + " beganFromNonInteractive=" + beganFromNonInteractive
+ " mShortPressOnPowerBehavior=" + mShortPressOnPowerBehavior);
// 此时 count 为 1
if (count == 2) {
// ...
} else if (count == 3) {
// ...
} else if (count > 3 && count <= getMaxMultiPressPowerCount()) {
// ...
} else if (count == 1 && interactive && !beganFromNonInteractive) { // 亮屏
// ...
// mShortPressOnPowerBehavior 是从配置中获取的
switch (mShortPressOnPowerBehavior) {
case SHORT_PRESS_POWER_NOTHING:
break;
case SHORT_PRESS_POWER_GO_TO_SLEEP:
sleepDefaultDisplayFromPowerButton(eventTime, 0);
break;
case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP:
sleepDefaultDisplayFromPowerButton(eventTime,
PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
break;
// ...
}
}
}
很奇怪吧,电源键的规则中,对于单击功能,只实现灭屏的功能。
而电源键单击亮屏功能,是在策略 PhoneWindowManager 中实现的,如下
// PhoneWindowManager.java
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
// ...
// Handle special keys.
switch (keyCode) {
// ...
final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
// ...
final boolean isDefaultDisplayOn = mDefaultDisplayPolicy.isAwake();
final boolean interactiveAndOn = interactive && isDefaultDisplayOn;
// ...
case KeyEvent.KEYCODE_POWER: {
// 电源键 event log
// 注意,在实际中有很大用处
EventLogTags.writeInterceptPower(
KeyEvent.actionToString(event.getAction()),
mPowerKeyHandled ? 1 : 0,
mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
// 电源键的 Key Event,不分发给窗口
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down) {
interceptPowerKeyDown(event, interactiveAndOn);
} else {
// ...
}
break;
}
// ...
}
// ...
return result;
}
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
// ...
// Stop ringing or end call if configured to do so when power is pressed.
TelecomManager telecomManager = getTelecommService();
boolean hungUp = false;
if (telecomManager != null) {
if (telecomManager.isRinging()) { // 来电静音
// Pressing Power while there's a ringing incoming
// call should silence the ringer.
telecomManager.silenceRinger();
} else if ((mIncallPowerBehavior
& Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
&& telecomManager.isInCall() && interactive) { // 通话挂断
// Otherwise, if "Power button ends call" is enabled,
// the Power button will hang up any current active call.
hungUp = telecomManager.endCall();
}
}
// 跟 sensor 灭屏有关
final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event);
// Inform the StatusBar; but do not allow it to consume the event.
sendSystemKeyToStatusBarAsync(event);
// If the power key has still not yet been handled, then detect short
// press, long press, or multi press and decide what to do.
mPowerKeyHandled = mPowerKeyHandled || hungUp
|| handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
if (!mPowerKeyHandled) { // 没有被用于其实用途
if (!interactive) { // 系统处于非交互状态,例如灭屏
// 亮屏
wakeUpFromPowerKey(event.getDownTime());
}
} else {
// ...
}
}