Android 7.0 Power 按鍵處理流程
Android 7.0 Power 按鍵處理流程
Power按鍵的處理邏輯由PhoneWindowManager來完成,本文只關注PhoneWindowManager中與Power鍵相關的內容,其他系統按鍵的處理類似也是在PhoneWindowManager中處理的。理解了power按鍵的處理再看其他系統按鍵的邏輯會容易的多也簡單的多。
一、Power按鍵的上報
Power按鍵的上報流程與其餘的按鍵處理流程一致,在按下power按鍵後驅動上報按鍵經InputManagerService處理按鍵事件,最終將會傳遞到PhoneWindowManager的interceptKeyBeforeQueueing函式來做具體的業務邏輯。(具體處理 Input system 做詳細介紹)本篇側重power的業務邏輯處理,這裡簡單介紹下power 按鍵的上報流程如下圖:
1-4:在power 按鍵按下時驅動會上報按鍵事件,EventHub讀取到事件後轉給InputReader來做處理。
5-9:InputReader根據上報的事件型別(此處是按鍵事件),交給KeyBoardInputMapper來做按鍵對映,根據驅動上報的按鍵值來對映為android framework的按鍵值(即KeyEvent.KEYCODE_POWER 和相應的flag),並通知上層。
10-16:經過層呼叫最後到PhoneWindowManager的interceptKeyBeforeQueueing()函式來做具體的業務處理。這是本文的重點,下面具體分析。
17-18:通過PowerManagerService來喚醒系統。
二、Power 按鍵處理
PhoneWindowManager的interceptKeyBeforeQueueing()函式處理具體的業務邏輯,從這個函式開始進行分析。
按鍵的事件分為按下和擡起兩個,framework的處理也是分為按下和擡起來不同的事件分別由不同的函式來處理。
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { if (!mSystemBooted) { // If we have not yet booted, don't let key events do anything.return 0; } //是否點亮螢幕 final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0; final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final boolean canceled = event.isCanceled(); //獲取按鍵對應的android framework層的按鍵編碼 final int keyCode = event.getKeyCode(); ................. switch (keyCode) { ........... case KeyEvent.KEYCODE_POWER: { result &= ~ACTION_PASS_TO_USER; isWakeKey = false; // wake-up will be handled separately if (down) { //處理Power鍵按下 interceptPowerKeyDown(event, interactive); } else { //處理Power鍵鬆開 interceptPowerKeyUp(event, interactive, canceled); } break; } } .............. if (isWakeKey) { //按power鍵時,isWakeKey置為false,於是不會呼叫wakeUp函式,即不會喚醒系統點亮螢幕 wakeUp(event.getEventTime(), mAllowTheaterModeWakeFromKey, "android.policy:KEY"); } return result; }
擴充套件:由上可知 isWakeKey 用來控制是否喚醒系統並點亮螢幕,如果需要新增按鍵需要實現點亮螢幕功能,可以在此處理 ^_^。
接下來,我們分別看一下interceptPowerKeyDown和interceptPowerKeyUp函式。
1、Power按鍵按下(interceptPowerKeyDown)
interceptPowerKeyDown()用於處理按下Power鍵(還未擡起)對應的事件。
A: Power按鍵按下處理時序圖
處理流程:
1) 判斷是否power按鍵與音量按鍵“同時”按下需要做截圖動作,是則觸發截圖。
2) 是否為響鈴或通話狀態,是則執行與之相關配置的動作(預設響鈴會靜音,如果設定按power結束通話電話則結束通話)。
3) 判斷是否為長按,是則根據配置執行長按行為的動作。
B: 程式碼分析
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { // Hold a wake lock until the power key is released. // mPowerKeyWakeLock為PARTIAL_WAKE_LOCK級別的鎖 if (!mPowerKeyWakeLock.isHeld()) { //將呼叫到PMS的acquire WakeLock流程 mPowerKeyWakeLock.acquire(); } // Cancel multi-press detection timeout. //處理多次按power鍵的場景 //每次power up時,傳送MSG_POWER_DELAYED_PRESS的延遲訊息 //如果延遲訊息被處理,說明一次完整的Power鍵處理結束(按下去,彈起來) if (mPowerKeyPressCounter != 0) { mHandler.removeMessages(MSG_POWER_DELAYED_PRESS); } // Detect user pressing the power button in panic when an application has // taken over the whole screen. boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(interactive, SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags)); if (panic) { mHandler.post(mHiddenNavPanic); } // Latch power key state to detect screenshot chord. //屏狀態,滿足觸發截圖的條件則觸發截圖功能 if (interactive && !mScreenshotChordPowerKeyTriggered && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { mScreenshotChordPowerKeyTriggered = true; mScreenshotChordPowerKeyTime = event.getDownTime(); //觸發截圖功能 interceptScreenshotChord(); } // 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. //如果正在接聽電話,且配置了Power鍵結束通話電話的話,按Power按鍵結束通話正在接聽的電話 hungUp = telecomManager.endCall(); } } GestureLauncherService gestureService = LocalServices.getService( GestureLauncherService.class); boolean gesturedServiceIntercepted = false; if (gestureService != null) { //手勢對應的服務,嘗試攔截處理Power鍵動作事件 gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive,mTmpBoolean); if (mTmpBoolean.value && mGoingToSleep) { mCameraGestureTriggeredDuringGoingToSleep = true; } } // 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 = hungUp ||mScreenshotChordVolumeDownKeyTriggered||mScreenshotChordVolumeUpKeyTriggered || gesturedServiceIntercepted; //Power鍵事件未被消耗掉 if (!mPowerKeyHandled) { //螢幕還是亮的 if (interactive) { // When interactive, we're already awake. // Wait for a long press or for the button to be released to decide what to do. //1、 判斷是否支援長按的行為 if (hasLongPressOnPowerBehavior()) { //2、 亮屏狀態,長按Power鍵將觸發MSG_POWER_LONG_PRESS訊息 Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); } } else { //3、滅屏狀態先喚醒系統,這個會呼叫到PMS的wakeUp wakeUpFromPowerKey(event.getDownTime()); //支援熄屏長按,mSupportLongPressPowerWhenNonInteractive讀資原始檔得到,預設為false if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) { Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); mBeganFromNonInteractive = true; } else { //預設返回1 final int maxCount = getMaxMultiPressPowerCount(); if (maxCount <= 1) { //息屏時,按下power鍵(不彈起),僅消耗掉該事件 mPowerKeyHandled = true; } else { mBeganFromNonInteractive = true; } } } } }
1)是否支援長按hasLongPressOnPowerBehavior ()
hasLongPressOnPowerBehavior負責判斷終端是否支援長按的行為,通過讀取系統的配置檔案來判斷。
private boolean hasLongPressOnPowerBehavior() { return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING; } private int getResolvedLongPressOnPowerBehavior() { //取決與系統屬性"factory.long_press_power_off",此處預設為false if (FactoryTest.isLongPressOnPowerOffEnabled()) { return LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM; } return mLongPressOnPowerBehavior; } 從上面的程式碼可以看出,終端是否支援長按行為,最終將由mLongPressOnPowerBehavior決定。 ......... mLongPressOnPowerBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_longPressOnPowerBehavior); .........
mLongPressOnPowerBehavior將在PhoneWindowManager初始化時,通過讀取資原始檔(定義在frameworks/base/res/res/values/config.xm 中)得到,一般情況下應該為1。 於是,hasLongPressOnPowerBehavior的值返回true,即終端支援Power鍵長按。
config.xm 中定義的power按鍵的長按、短按、雙擊 、三聯按的行為
2)長按MSG_POWER_LONG_PRESS的處理
從上面的程式碼,在亮屏時按Power鍵,會觸發延遲的MSG_POWER_LONG_PRESS訊息。 如果在MSG_POWER_LONG_PRESS超時前,Power鍵未被釋放掉,那麼此次操作被定義為長按Power鍵。
MSG_POWER_LONG_PRESS對應的處理函式為powerLongPress:
private void powerLongPress() { //也是由資原始檔得到,預設為1,即LONG_PRESS_POWER_GLOBAL_ACTIONS final int behavior = getResolvedLongPressOnPowerBehavior(); switch (behavior) { case LONG_PRESS_POWER_NOTHING: break; case LONG_PRESS_POWER_GLOBAL_ACTIONS: mPowerKeyHandled = true; //performHapticFeedbackLw進行震動反饋,不同的事件,定義了不同的震動模式 if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) { //如果沒有震動反饋,嘗試聲音反饋,例如響一下按鍵音 performAuditoryFeedbackForAccessibilityIfNeed(); } //彈出選擇關機還是重啟的對話方塊 showGlobalActionsInternal(); break; case LONG_PRESS_POWER_SHUT_OFF: case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM: mPowerKeyHandled = true; performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false); //彈出系統關機介面 sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); //調關機介面 mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF); break; ......... } }
3)喚醒系統點亮螢幕wakeUpFromPowerKey ()
在息屏的狀態下按下Power鍵,將呼叫wakeUpFromPowerKey函式喚醒系統:
private void wakeUpFromPowerKey(long eventTime) { //從config.xml讀mAllowTheaterModeWakeFromPowerKey預設為true wakeUp(eventTime, mAllowTheaterModeWakeFromPowerKey, "android.policy:POWER"); } private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, String reason) { //取資料庫的值 final boolean theaterModeEnabled = isTheaterModeEnabled(); //按Power鍵時,條件返回false if (!wakeInTheaterMode && theaterModeEnabled) { return false; } if (theaterModeEnabled) { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0); } //最終將呼叫到PMS的wakeUp函式 mPowerManager.wakeUp(wakeTime, reason); return true; } 我們跟進一下PMS的wakeUp函式: public void wakeUp(long eventTime, String reason, String opPackageName) { .......... try { wakeUpInternal(eventTime, reason, uid, opPackageName, uid); } finally { ............... } } private void wakeUpInternal(long eventTime, String reason, int uid, String opPackageName, int opUid) { synchronized (mLock) { //更新Wakefullness的狀態為WAKEFULNESS_AWAKE,記錄一次UserActivity if (wakeUpNoUpdateLocked(eventTime, reason, uid, opPackageName, opUid)) { //如之前部落格所述,對整個電源狀態進行一次調整,將在需要時點亮螢幕 updatePowerStateLocked(); } } }
2、Power擡起(interceptPowerKeyUp )
A: Power擡起 時序圖
interceptPowerKeyUp處理鬆開Power鍵後的流程:
B: Power擡起程式碼分析
private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) { //事件被取消,或者在按下Power鍵時,該事件已被消耗掉 //那麼就不用繼續處理 final boolean handled = canceled || mPowerKeyHandled; mScreenshotChordPowerKeyTriggered = false; //退出截圖 cancelPendingScreenshotChordAction(); //取消MSG_POWER_LONG_PRESS事件,即在一定事件內Power鍵彈起,則表示這一次不是長按Power鍵 cancelPendingPowerKeyAction(); //從之前的程式碼,我們知道除了特殊功能外滅屏按Power鍵或亮屏長按時,均會消耗掉Power事件因此,只有亮屏短按Power鍵需要進行處理 if (!handled) { // Figure out how to handle the key now that it has been released. mPowerKeyPressCounter += 1; final int maxCount = getMaxMultiPressPowerCount(); final long eventTime = event.getDownTime(); if (mPowerKeyPressCounter < maxCount) { // This could be a multi-press. Wait a little bit longer to confirm. // Continue holding the wake lock. // 與之前interceptPowerKeyDown,處理Power鍵被多次按下場景對應 // 每次被按下,均傳送MSG_POWER_DELAYED_PRESS訊息 // 實際上maxCount為1,不會進入該分支 Message msg = mHandler.obtainMessage(MSG_POWER_DELAYED_PRESS, interactive ? 1 : 0, mPowerKeyPressCounter, eventTime); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, ViewConfiguration.getDoubleTapTimeout()); return; } //1、No other actions. Handle it immediately. powerPress(eventTime, interactive, mPowerKeyPressCounter); } // 2、Done. Reset our state. finishPowerKeyPress(); }
1)powerPress
powerPress函式根據螢幕狀態和配置的條件,如果亮屏狀態下短按power按鍵則呼叫PMS的goToSleep()函式,執行滅屏並將系統休眠。
private void powerPress(long eventTime, boolean interactive, int count) { if (mScreenOnEarly && !mScreenOnFully) { Slog.i(TAG, "Suppressed redundant power key press while " + "already in the process of turning the screen on."); return; } if (count == 2) { //原生不進入 powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior); } else if (count == 3) { //原生不進入 powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior); } else if (interactive && !mBeganFromNonInteractive) { //亮屏時,將進入該分支 //mShortPressOnPowerBehavior被配置為1 switch (mShortPressOnPowerBehavior) { case SHORT_PRESS_POWER_NOTHING: break; case SHORT_PRESS_POWER_GO_TO_SLEEP: //最終呼叫到PMS的goToSleep函式 mPowerManager.goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0); break; ............... } } } 在亮屏狀態下,短按一下Power鍵,最終將呼叫到PMS的goToSleep函式,使終端進入到休眠狀態,與實際情況一致。 PMS的goToSleep函式 public void goToSleep(long eventTime, int reason, int flags) { ............ try { goToSleepInternal(eventTime, reason, flags, uid); } finally { ............... } } private void goToSleepInternal(long eventTime, int reason, int flags, int uid) { synchronized (mLock) { //沒有觸發使用者事件,將mWakefullness置為WAKEFULNESS_DOZING if (goToSleepNoUpdateLocked(eventTime, reason, flags, uid)) { //執行整體的電源狀態更新,將熄滅螢幕 updatePowerStateLocked(); } }
2)finishPowerKeyPress
每當處理一次完整的Power鍵按下、彈出操作後,interceptPowerKeyUp呼叫finishPowerKeyPress進行最後的狀態復位操作:
private void finishPowerKeyPress() { mBeganFromNonInteractive = false; mPowerKeyPressCounter = 0; if (mPowerKeyWakeLock.isHeld()) { mPowerKeyWakeLock.release(); } }
從程式碼可以看出,主要的工作其實就是將狀態變數恢復為初始值,同時釋放掉最初申請的鎖。
三、Power按鍵處理總流程
按鍵處理的總流程如下圖: