網站首頁 編程語言 正文
引言
上一篇文章 Input系統: 按鍵事件分發 分析了按鍵事件的分發過程,雖然分析的對象只是按鍵事件,但是也從整體上,描繪了事件分發的過程。其中比較有意思的一環是事件截斷策略,本文就來分析它的原理以及應用。
其實這篇文章早已經寫好,這幾天在完善細節的時候,我突然發現了源碼中的一個 bug,這讓我開始對自己的分析產生質疑,最終我拿了一臺公司的樣機 debug,我發現這確實是源碼的一個 bug。本文在分析的過程中,會逐步揭開這個 bug。
截斷策略的原理
根據 Input系統: 按鍵事件分發 的分析,事件開始分發前,會執行截斷策略,如下
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
// ...
uint32_t policyFlags = args->policyFlags;
int32_t flags = args->flags;
int32_t metaState = args->metaState;
// ...
// 根據事件參數創建 KeyEvent,截斷策略需要使用它
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);
android::base::Timer t;
// 1. 執行截斷策略,執行的結果保存到參數 policyFlags
mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
ALOGW("Excessive delay in interceptKeyBeforeQueueing; took %s ms",
std::to_string(t.duration().count()).c_str());
}
bool needWake;
{ // acquire lock
mLock.lock();
if (shouldSendKeyToInputFilterLocked(args)) {
// ...
}
// 2. 創建 KeyEntry , 并加入到 InputDispatcher 的收件箱中
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));
mLock.unlock();
} // release lock
// 3. 如果有必要,喚醒 InputDispatcher 線程
if (needWake) {
mLooper->wake();
}
}
根據 Input系統: InputManagerService的創建與啟動 可知,底層的截斷策略實現類是 NativeInputManager
// com_android_server_input_InputManagerService.cpp
void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
uint32_t& policyFlags) {
bool interactive = mInteractive.load();
if (interactive) {
policyFlags |= POLICY_FLAG_INTERACTIVE;
}
// 來自輸入設備的按鍵事件是受信任的
// 擁有注入權限的app,注入的按鍵事件也是受信任的,例如 SystemUI 注入 HOME, BACK, RECENT 按鍵事件
if ((policyFlags & POLICY_FLAG_TRUSTED)) {
nsecs_t when = keyEvent->getEventTime();
JNIEnv* env = jniEnv();
// 包裝成上層的 KeyEvent 對象
jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
jint wmActions;
if (keyEventObj) {
// 1. 調用上層 InputManagerService#interceptKeyBeforeQueueing() 來執行截斷策略
wmActions = env->CallIntMethod(mServiceObj,
gServiceClassInfo.interceptKeyBeforeQueueing,
keyEventObj, policyFlags);
if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
wmActions = 0;
}
android_view_KeyEvent_recycle(env, keyEventObj);
env->DeleteLocalRef(keyEventObj);
} else {
ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
wmActions = 0;
}
// 2. 處理截斷策略的結果
// 實際上就是把上層截斷策略的結果轉化為底層的狀態
handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
} else {
// ...
}
}
void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
uint32_t& policyFlags) {
// 其實就是根據截斷策略的結果,決定是否在 policyFlags 添加 POLICY_FLAG_PASS_TO_USER 標志位
if (wmActions & WM_ACTION_PASS_TO_USER) {
policyFlags |= POLICY_FLAG_PASS_TO_USER;
} else {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("handleInterceptActions: Not passing key to user.");
#endif
}
}
首先調用上層來執行截斷策略,然后根據執行的結果,再決定是否在參數 policyFlags 添加 POLICY_FLAG_PASS_TO_USER 標志位。這個標志位,就決定了事件是否能傳遞給用戶。
截斷策略經過 InputManagerService,最終是由上層的 PhoneWindowManager 實現,如下
// PhoneWindowManager.java
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
// ...
// Handle special keys.
switch (keyCode) {
// ...
case KeyEvent.KEYCODE_POWER: {
// 返回的額結果去,去掉 ACTION_PASS_TO_USER 標志位
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;
}
這里以 power 按鍵為例,它的按鍵事件的截斷策略的處理結果,是去掉了 ACTION_PASS_TO_USER 標志位,也即告訴底層不要把事件發送給用戶,這就是為何窗口(例如 Activity)無法收到 power 按鍵事件的原因。
現在,如果項目的硬件上新增一個按鍵,并且不想這個按鍵事件被分發給用戶,你會搞了嗎?
截斷策略的應用
根據 Input系統: 按鍵事件分發 可知,截斷策略發生在事件分發之前,因此它能及時處理一些系統功能的事件,例如,power 按鍵亮/滅屏沒有延時,掛斷按鍵掛斷電話也沒有延時。
剛才,我們看到了截斷策略的一個作用,阻塞事件發送給用戶/窗口。然而,它還可以實現按鍵的手勢,手勢包括單擊,多擊,長按,組合鍵(例如截屏組合鍵)。
下面來分析截斷策略是如何實現按鍵手勢中的單擊、多擊、長按。至于組合鍵,由于涉及分發策略,留到下一篇文章分析。
初始化
按鍵的手勢是用 SingleKeyGestureDetector 來管理的,它在 PhoneWindowManager 中的初始化如下
// PhoneWindowManager.java
private void initSingleKeyGestureRules() {
mSingleKeyGestureDetector = new SingleKeyGestureDetector(mContext);
int powerKeyGestures = 0;
if (hasVeryLongPressOnPowerBehavior()) {
powerKeyGestures |= KEY_VERYLONGPRESS;
}
if (hasLongPressOnPowerBehavior()) {
powerKeyGestures |= KEY_LONGPRESS;
}
// 增加一個 power 按鍵手勢的規則
mSingleKeyGestureDetector.addRule(new PowerKeyRule(powerKeyGestures));
if (hasLongPressOnBackBehavior()) {
mSingleKeyGestureDetector.addRule(new BackKeyRule(KEY_LONGPRESS));
}
}
SingleKeyGestureDetector 根據配置,為 Power 鍵和 Back 鍵保存了規則(rule)。所謂的規則,就是如何實現單個按鍵的手勢。
所有的規則的基類都是 SingleKeyGestureDetector.SingleKeyRule,它的使用方式用下面一段代碼解釋
SingleKeyRule rule =
new SingleKeyRule(KEYCODE_POWER, KEY_LONGPRESS|KEY_VERYLONGPRESS) {
int getMaxMultiPressCount() { // maximum multi press count. }
void onPress(long downTime) { // short press behavior. }
void onLongPress(long eventTime) { // long press behavior. }
void onVeryLongPress(long eventTime) { // very long press behavior. }
void onMultiPress(long downTime, int count) { // multi press behavior. }
};
- getMaxMultiPressCount() 表示支持的按鍵的最大點擊次數。如果返回1,表示只支持單擊,如果返回3,表示支持雙擊和三擊,and so on...
- 單擊按鍵會調用 onPress()。
- 多擊按鍵會調用 onMultiPress(long downTime, int count),參數 count 表示多擊的次數。
- 長按按鍵會調用 onLongPress()。
- 長時間地長按按鍵,會調用 onVeryLongPress()。
實現按鍵手勢
截斷策略在處理按鍵事件時,會處理按鍵手勢,如下
// PhoneWindowManager.java
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
// ...
// 一般來說,軌跡球設備產生的事件,會設置 KeyEvent.FLAG_FALLBACK
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
// 處理按鍵手勢
handleKeyGesture(event, interactiveAndOn);
}
// ...
return result;
}
private void handleKeyGesture(KeyEvent event, boolean interactive) {
// KeyCombinationManager 是用于實現組合按鍵功能,如果只按下單個按鍵,不會截斷事件
if (mKeyCombinationManager.interceptKey(event, interactive)) {
// handled by combo keys manager.
mSingleKeyGestureDetector.reset();
return;
}
// GestureLauncherService 實現的雙擊打開 camera 功能
// 原理很簡單,就是判斷兩次 power 鍵按下的時間間隔
if (event.getKeyCode() == KEYCODE_POWER && event.getAction() == KeyEvent.ACTION_DOWN) {
mPowerKeyHandled = handleCameraGesture(event, interactive);
if (mPowerKeyHandled) {
// handled by camera gesture.
mSingleKeyGestureDetector.reset();
return;
}
}
// 實現按鍵手勢
mSingleKeyGestureDetector.interceptKey(event, interactive);
}
從這里可以看到,有三個類實現按鍵的手勢,如下
- KeyCombinationManager,它是用于實現組合按鍵的功能,例如,power 鍵 + 音量下鍵 實現的截屏功能。它的原理很簡單,就是第一個按鍵按下后,在超時時間內等待第二個按鍵事件的到來。
- GestureLauncherService,目前只實現了雙擊打開 Camera 功能,原理也很簡單,當第一次按下 power 鍵,在規定時間內按下第二次 power 鍵,然后由 SystemUI 實現打開 Camera 功能。
- SingleKeyGestureDetector,實現通用的按鍵的手勢功能。
GestureLauncherService 在很多個 Android 版本中,都只實現了雙擊打開 Camera 的功能,它的功能明顯與 SingleKeyGestureDetector 重合了。然而,更不幸的是,SingleKeyGestureDetector 實現的手勢功能還有 bug,Google 的工程師是不是把這個按鍵手勢功能給遺忘了?
KeyCombinationManager 會在下一篇文章中分析,GestureLauncherService 請大家自行分析,現在來看下 SingleKeyGestureDetector 是如何處理按鍵手勢的
// SingleKeyGestureDetector.java
void interceptKey(KeyEvent event, boolean interactive) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (mDownKeyCode == KeyEvent.KEYCODE_UNKNOWN || mDownKeyCode != event.getKeyCode()) {
// 記錄按鍵按下時,是否是非交互狀態
// 一般來說,滅屏狀態就是非交互狀態
mBeganFromNonInteractive = !interactive;
}
interceptKeyDown(event);
} else {
interceptKeyUp(event);
}
}
SingleKeyGestureDetector 分別處理了按鍵的 DOWN 事件和 UP 事件,這兩者合起來才實現了整個手勢功能。
首先看下如何處理 DOWN 事件
// SingleKeyGestureDetector.java
private void interceptKeyDown(KeyEvent event) {
final int keyCode = event.getKeyCode();
// 3. 收到同一個按鍵的長按事件,立即執行長按動作
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);
// 立即執行長按動作
// 注意,是立即,因為系統已經表示這是一個長按動作
mActiveRule.onLongPress(event.getEventTime());
}
return;
}
// 表示這里前一個按鍵按下還沒有抬起前,又有另外一個按鍵按下
if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN
|| (mActiveRule != null && !mActiveRule.shouldInterceptKey(keyCode))) {
if (DEBUG) {
Log.i(TAG, "Press another key " + KeyEvent.keyCodeToString(keyCode));
}
reset();
}
// 保存按下的按鍵 keycode
mDownKeyCode = keyCode;
// 1. 按下首次按下,尋找一個規則
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)) {
mActiveRule = rule;
// 找到有效的 rule,就退出循環
// 看來對于一個按鍵,只有最先添加的規則有效
break;
}
}
}
// 沒有為按鍵事件找到一條規則,直接退出
if (mActiveRule == null) {
return;
}
final long eventTime = event.getEventTime();
// 2. 首次按下時,發送一個長按的延時消息,用于實現按鍵的長按功能
// mKeyPressCounter 記錄的是按鍵按下的次數
if (mKeyPressCounter == 0) {
if (mActiveRule.supportLongPress()) {
final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0,
eventTime);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg, mLongPressTimeout);
}
if (mActiveRule.supportVeryLongPress()) {
final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0,
eventTime);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg, mVeryLongPressTimeout);
}
}
// 4. 這里表示之前已經按鍵已經按下至少一次
else {
// 移除長按事件的延時消息
mHandler.removeMessages(MSG_KEY_LONG_PRESS);
mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
// 移除單擊事件或多擊事件的延時消息
mHandler.removeMessages(MSG_KEY_DELAYED_PRESS);
// Trigger multi press immediately when reach max count.( > 1)
// 達到最大點擊次數,立即執行多擊功能
// 注意,這段代碼是一個 bug
if (mKeyPressCounter == mActiveRule.getMaxMultiPressCount() - 1) {
if (DEBUG) {
Log.i(TAG, "Trigger multi press " + mActiveRule.toString() + " for it"
+ " reach the max count " + mKeyPressCounter);
}
mActiveRule.onMultiPress(eventTime, mKeyPressCounter + 1);
mKeyPressCounter = 0;
}
}
}
SingleKeyGestureDetector 對按鍵 DOWN 事件的處理過程如下
- 按鍵首次按下,為按鍵找到相應的規則,保存到 mActiveRule。
- 按鍵首次按下,會發送一個延時的長按消息,實現長按功能。當超時時,也就是按鍵按下沒有抬起,并且系統也沒有發送按鍵的長按事件,那么會執行 SingleKeyRule#onLongPress() 或/和 SingleKeyRule#onVeryLongPress()。
- 如果收到系統發送的按鍵的長按事件,那么移除長按消息,并立即SingleKeyRule#onLongPress()。為何要立即執行,而不是發送一個延時消息?因為系統已經表示這是一個長按事件,沒有理由再使用一個延時來檢測是否要觸發長按。
- 如果多次(至少超過1次)點擊按鍵,那么移除長按、單擊/多擊消息,并在點擊次數達到最大時,立即執行SingleKeyRule#onMultiPress()。
注意,第4點中,當達到最大點擊次數時,立即執行SingleKeyRule#onMultiPress(),并重置按鍵點擊次數 mKeyPressCounter 為 0,這是一個 Bug,這段代碼應該去掉。在后面的分析中,我將證明這會造成 bug。
SingleKeyGestureDetector 對按鍵按下事件的處理,確切來說只實現了長按的功能,而按鍵的單擊功能以及多擊功能,是在處理 UP 事件中實現的,如下
// SingleKeyGestureDetector.java
private boolean interceptKeyUp(KeyEvent event) {
// 按鍵已抬起,就不應該觸發長按事件,所以需要移除延時的長按消息
mHandler.removeMessages(MSG_KEY_LONG_PRESS);
mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS);
// 按鍵抬起,重置 mDownKeyCode
mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN;
// 沒有有效規則,不處理按鍵的抬起事件
if (mActiveRule == null) {
return false;
}
// 如果已經觸發長按,不處理按鍵的抬起事件
if (mHandledByLongPress) {
mHandledByLongPress = false;
mKeyPressCounter = 0;
return true;
}
final long downTime = event.getDownTime();
// 抬起按鍵的key code 要與規則的一樣,否則無法觸發規則
if (event.getKeyCode() == mActiveRule.mKeyCode) {
// 1. 規則只支持單擊,那么發送消息,執行單擊操作。
if (mActiveRule.getMaxMultiPressCount() == 1) {
// 注意,第三個參數為 arg2,但并沒有使用
Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
1, downTime);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
return true;
}
// 走到這里,表示規則支持多擊功能,那么必須記錄多擊的次數,用于實現按鍵多擊功能
mKeyPressCounter++;
// 2. 規則支持多擊,發送一個延遲消息來實現單擊或者多擊功能
// 既然支持多擊,那么肯定需要一個超時時間來檢測是否有多擊操作,所以這里要設置一個延時
// 注意,第三個參數為 arg2,但并沒有使用
Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
mKeyPressCounter, downTime);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT);
return true;
}
// 收到其他按鍵的 UP/CANCEL 事件,重置前一個按鍵的規則
reset();
return false;
}
SingleKeyGestureDetector 處理 UP 事件分兩種情況
- 如果規則只支持單擊,那么發送一個消息執行單擊動作。這里我就有一個疑問了,為何不與前面一樣,直接執行單擊動作,反而還要多此一舉地發送一個消息去執行單擊動作?
- 如果規則支持多擊,那么首先把點擊次數 mKeyPressCounter 加1,然后發送一個延時消息執行單擊或者多擊動作。為何要設置一個延時?對于單擊操作,需要使用一個延時來檢測沒有再次點擊的操作,對于多擊操作,需要檢測后面是否還有點擊操作。
注意,以上兩種情況下發送的消息,Message#arg2 的值其實是按鍵的點擊次數。
現在來看下 MSG_KEY_DELAYED_PRESS 消息的處理流程。
// SingleKeyGestureDetector.java
private class KeyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
if (mActiveRule == null) {
return;
}
final int keyCode = msg.arg1;
final long eventTime = (long) msg.obj;
switch(msg.what) {
// ...
case MSG_KEY_DELAYED_PRESS:
// 雖然在發送消息的時候,使用了 Message#arg2 參數來表示按鍵的點擊次數
// 但是,這里仍然使用 mKeyPressCounter 來決定按鍵的點擊次數
if (mKeyPressCounter == 1) {
// 當規則支持多擊功能時,單擊功能由這里實現
mActiveRule.onPress(eventTime);
} else {
// 當規則只支持單擊功能時,mKeyPressCounter 永遠為 0,因此單擊功能由這里實現
// 當規則支持多擊功能時,多擊功能由這里實現
mActiveRule.onMultiPress(eventTime, mKeyPressCounter);
}
reset();
break;
}
}
}
這個消息的處理過程,從表面上看,如果是單擊就執行 SingleKeyRule#onPress(),如果是多擊就執行 SingleKeyRule#onMultiPress()。
然而實際情況,并非如此。由于沒有使用 Message#arg2,而是直接使用 mKeyPressCounter 作為按鍵點擊次數,這就導致兩個問題
- 當規則只支持單擊功能時,mKeyPressCounter 永遠為0。于是單擊按鍵時,調用 SingleKeyRule#onMultiPress(),而非 SingleKeyRule#onPress(),這豈不是笑話。
- 當規則支持多擊功能時,如果單擊按鍵,會調用 SingleKeyRule#onPress(),如果多擊按鍵,會調用 SingleKeyRule#onMultiPress() 這一切看起來沒有問題,實則不然。對于多擊操作,前面分析過,在處理 DOWN 事件時,當達到最大點擊次數時,會調用SingleKeyRule#onMultiPress(),并把 mKeyPressCounter 重置為0。之后再處理 UP 事件時,mKeyPressCounter 加1后變為了1,然后發送消息去執行,最后,奇跡般地執行了一次 SingleKeyRule#onPress()。對于一個多擊操作,居然執行了一次單擊動作,這簡直是國際笑話。
以上兩點問題,我用樣機進行驗證過,確實存在。但是,系統很好地支持了雙擊 power 打開 Camera,以及單擊 power 亮/滅屏。這又是怎么回事呢?
- 雙擊 power 打開 Camera,是由 GestureLauncherService 實現的。
- 系統默認配置的 power 規則,只支持單擊。
既然 power 規則只支持單擊,理論上應該調用 SingleKeyRule#onMultiPress(long downTime, int count),并且參數 count 為0,這豈不還是錯的。確實如此,不過源碼又巧合地用另外一個Bug避開了這一個Bug。接著往下看
首先看下 power 鍵的規則
// PhoneWindowManager.java
private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
PowerKeyRule(int gestures) {
super(KEYCODE_POWER, gestures);
}
@Override
int getMaxMultiPressCount() {
// 默認配置返回1
return getMaxMultiPressPowerCount();
}
@Override
void onPress(long downTime) {
powerPress(downTime, 1 /*count*/,
mSingleKeyGestureDetector.beganFromNonInteractive());
}
@Override
void onLongPress(long eventTime) {
if (mSingleKeyGestureDetector.beganFromNonInteractive()
&& !mSupportLongPressPowerWhenNonInteractive) {
Slog.v(TAG, "Not support long press power when device is not interactive.");
return;
}
powerLongPress(eventTime);
}
@Override
void onVeryLongPress(long eventTime) {
mActivityManagerInternal.prepareForPossibleShutdown();
powerVeryLongPress();
}
@Override
void onMultiPress(long downTime, int count) {
powerPress(downTime, count, mSingleKeyGestureDetector.beganFromNonInteractive());
}
}
power 鍵規則,默認支持最大的點擊數為1,這是在 config.xml 中進行配置的,這里不細講。
power 鍵規則中,無論是單擊還是多擊,默認都調用同一個函數,如下
private void powerPress(long eventTime, int count, boolean beganFromNonInteractive) {
if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) {
Slog.i(TAG, "Suppressed redundant power key press while "
+ "already in the process of turning the screen on.");
return;
}
final boolean interactive = Display.isOnState(mDefaultDisplay.getState());
Slog.d(TAG, "powerPress: eventTime=" + eventTime + " interactive=" + interactive
+ " count=" + count + " beganFromNonInteractive=" + beganFromNonInteractive
+ " mShortPressOnPowerBehavior=" + mShortPressOnPowerBehavior);
// 根據配置,決定power鍵的單擊/多擊行為
if (count == 2) {
powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior);
} else if (count == 3) {
powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
}
// 注意,beganFromNonInteractive 表示是否在非交互狀態下點擊 power 鍵
// 這里判斷條件的意思是,處于交互狀態,并且不是非交互狀態下點擊power鍵
// 說簡單點,這里只支持在亮屏狀態下單擊power鍵功能
else if (interactive && !beganFromNonInteractive) {
if (mSideFpsEventHandler.onSinglePressDetected(eventTime)) {
Slog.i(TAG, "Suppressing power key because the user is interacting with the "
+ "fingerprint sensor");
return;
}
switch (mShortPressOnPowerBehavior) {
case SHORT_PRESS_POWER_NOTHING:
break;
case SHORT_PRESS_POWER_GO_TO_SLEEP:
// 滅屏,不過要先進入 doze 模式
sleepDefaultDisplayFromPowerButton(eventTime, 0);
break;
case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP:
// 跳過 doze 模式,直接進入 sleep 模式
sleepDefaultDisplayFromPowerButton(eventTime,
PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
break;
case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME:
// 跳過 doze 模式,進入 sleep 模式,并返回 home
if (sleepDefaultDisplayFromPowerButton(eventTime,
PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE)) {
launchHomeFromHotKey(DEFAULT_DISPLAY);
}
break;
case SHORT_PRESS_POWER_GO_HOME:
// 返回 home
shortPressPowerGoHome();
break;
case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME: {
if (mDismissImeOnBackKeyPressed) {
// 關閉輸入法
if (mInputMethodManagerInternal == null) {
mInputMethodManagerInternal =
LocalServices.getService(InputMethodManagerInternal.class);
}
if (mInputMethodManagerInternal != null) {
mInputMethodManagerInternal.hideCurrentInputMethod(
SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME);
}
} else {
// 返回 home
shortPressPowerGoHome();
}
break;
}
}
}
}
從整體看,如果參數 count 為2或者3,會執行多擊的行為,否則,執行單擊行為。
這個邏輯是不是有點奇怪呀,參數 count 不為2也不為3,難道就是一定為1嗎?正是因為這個邏輯,才導致 count 為0時,也能執行單擊動作,小朋友聽了都直呼6。我想起了我一個前同事做的事,用一個 Bug 去解決另外一個 Bug。
power 鍵的亮屏與滅屏
好了,言歸正傳,power 鍵規則的單擊行為,只包括了滅屏,并沒有包含亮屏,這又是怎么回事呢?因為 power 鍵的亮屏,不是在規則中實現的,而是在截斷策略中實現的,如下
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
// ...
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
// 處理單個按鍵的手勢
handleKeyGesture(event, interactiveAndOn);
}
// ...
switch (keyCode) {
// ...
case KeyEvent.KEYCODE_POWER: {
// power 按鍵事件不分發給用戶
result &= ~ACTION_PASS_TO_USER;
// 這里表示 power 鍵不是喚醒鍵,是不是很奇怪,因為系統默認綁定了 power 鍵亮屏功能
isWakeKey = false; // wake-up will be handled separately
if (down) {
// power 亮屏在這里實現
interceptPowerKeyDown(event, interactiveAndOn);
} else {
interceptPowerKeyUp(event, canceled);
}
break;
}
// ...
}
// ...
return result;
}
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
// Hold a wake lock until the power key is released.
if (!mPowerKeyWakeLock.isHeld()) {
mPowerKeyWakeLock.acquire();
}
mWindowManagerFuncs.onPowerKeyDown(interactive);
// 設備處于響鈴狀態,就靜音,處于通話狀態,就掛電話
TelecomManager telecomManager = getTelecommService();
boolean hungUp = false;
if (telecomManager != null) {
if (telecomManager.isRinging()) {
telecomManager.silenceRinger();
} else if ((mIncallPowerBehavior
& Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
&& telecomManager.isInCall() && interactive) {
hungUp = telecomManager.endCall();
}
}
// 檢測 PowerManagerService 是否正在使用 sensor 滅屏
final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event);
sendSystemKeyToStatusBarAsync(event.getKeyCode());
// mPowerKeyHandled 在長按,組合鍵,雙擊打開Camera情況下,會被設置為 true
mPowerKeyHandled = mPowerKeyHandled || hungUp
|| handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted();
if (!mPowerKeyHandled) {
// power 事件沒有被其它地方使用,那么在滅屏狀態下執行亮屏
if (!interactive) {
wakeUpFromPowerKey(event.getDownTime());
}
} else {
// power 事件被其它地方使用,那么重置規則
if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) {
mSingleKeyGestureDetector.reset();
}
}
}
截斷策略處理 power 按鍵的 DOWN 事件過程如下
- 如果 power 按鍵事件沒有被其它地方使用,那么,在非交互狀態下,一般指滅屏狀態,會執行亮屏。
- 如果 power 按鍵事件被其它地方使用,那么重置按鍵手勢。
這里我又有一個疑問,為何要把 power 亮屏的代碼的在這里單獨處理?在創建規則時設置一個回調,是不是更好呢?
結束
我在支援公司的某個項目時,偶然發現有人在截斷策略中,要實現一個新的雙擊power功能,以及添加三擊power的功能,可謂是把源碼修改得"雞飛狗跳",我當時還嗤之以鼻,現在發現我錯怪他了。但是呢,由于他不知道如何修復這個源碼的 bug,所以他實現的過程還是非常丑陋。
最后,給看我文章的人一個小福利,你可以參考 Input系統: InputReader 處理按鍵事件 ,學會從底層映射一個按鍵到上層,然后根據本文,實現按鍵的手勢,這絕壁是一個非常叼的事情。當然,如果手勢中要包括多擊功能,還得解決本文提出的 Bug,這個不難,小小思考下就可以了。
原文鏈接:https://juejin.cn/post/7194457165984170021/
相關推薦
- 2022-07-02 iview中的表格render搭配使用Tooltip 文字提示
- 2023-10-24 開發項目中各環境的縮寫說明(DEV、SIT、UAT、PET、SIM、PRD/PROD)
- 2022-04-12 Oracle?Session每日統計功能實現_oracle
- 2022-04-21 Ubuntu16.04系統搭建.Net?Core開發環境_實用技巧
- 2022-04-10 MyBatis 查詢的時候屬性名和字段名不一致的問題
- 2022-04-07 WPF實現數據綁定_實用技巧
- 2022-11-13 Python中np.random.randint()參數詳解及用法實例_python
- 2022-05-26 Flutter實現滑動塊驗證碼功能_Android
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支