1. 程式人生 > >Android 7.0 Power 按鍵處理流程

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按鍵處理總流程

按鍵處理的總流程如下圖: