1. 程式人生 > >Android7.0 PowerManagerService(4) Power按鍵流程

Android7.0 PowerManagerService(4) Power按鍵流程

按鍵的處理主要由InputManagerService負責,屬於Android輸入系統的流程。在這篇部落格裡,我們只關注與Power鍵相關的內容。InputManagerService處理的按鍵事件,最終將會傳遞到PhoneWindowManager的interceptKeyBeforeQueueing函式。
我們就從這個函式開始,逐步進行分析。

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(); //按鍵對應的編碼 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; }

接下來,我們分別看一下interceptPowerKeyDown和interceptPowerKeyUp函式。

一、interceptPowerKeyDown

interceptPowerKeyDown用於處理按下Power鍵(還未鬆手釋放)對應的事件。

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鍵處理結束(按下去,彈起來)
    //在延遲訊息被處理前,再次按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.
    // 從註釋來看及mHiddenNavPanic的程式碼,覺得像是處理“誤觸”的
    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.
            //如果有電話撥入,且電話鈴聲響起,按Power鍵,設定電話響鈴靜音
            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.
    // Power鍵事件的處理,就像處理螢幕上的點選事件一樣,也依賴於事件分發機制
    // 如果已經被消耗掉了,就不會再被繼續處理
    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初始化時,通過讀取資原始檔得到,一般情況下應該為1。
於是,hasLongPressOnPowerBehavior的值返回true,即終端支援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;
        .........
    }
}

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();
        }
    }
}

二、interceptPowerKeyUp
interceptPowerKeyUp處理鬆開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函式:

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按鍵的主要處理流程如上圖所示。結合手機的實際情況,整個邏輯還是非常好理解的。