1. 程式人生 > >android4.4 車載滅屏 按任意鍵及觸控式螢幕幕恢復亮屏

android4.4 車載滅屏 按任意鍵及觸控式螢幕幕恢復亮屏

車載上的android4.4系統,基本上常亮。但最近需要一個新功能可以在launcher新增一個按鈕,點選的時候。螢幕亮度為0,但實際上不等於按power鍵,不會睡眠。

然後可以按任意鍵恢復亮度,包括觸屏事件。

一、PowerManagerService原先螢幕亮度流程

PowerManagerService是通過updateDisplayPowerStateLocked函式,把亮度更新到DisplayPowerController那塊,然後再去呼叫lightsService獲取背光的light,再去設定背景光的亮度。

但是這有問題,在PowerManagerService的updateDisplayPowerStateLocked函式,通常有個最低的亮度值(一般為10),如果低於這個亮度還是為這個值。

所以呼叫PowerManager的setBacklightBrightness,哪怕設的為0,最終亮度也為10.

    private void updateDisplayPowerStateLocked(int dirty) {
        if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS
                | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED
                | DIRTY_SETTINGS | DIRTY_SCREEN_ON_BLOCKER_RELEASED)) != 0) {
            int newScreenState = getDesiredScreenPowerStateLocked();
            if (newScreenState != mDisplayPowerRequest.screenState) {
                if (newScreenState == DisplayPowerRequest.SCREEN_STATE_OFF
                        && mDisplayPowerRequest.screenState
                                != DisplayPowerRequest.SCREEN_STATE_OFF) {
                    mLastScreenOffEventElapsedRealTime = SystemClock.elapsedRealtime();
                }

                mDisplayPowerRequest.screenState = newScreenState;
                nativeSetPowerState(
                        newScreenState != DisplayPowerRequest.SCREEN_STATE_OFF,
                        newScreenState == DisplayPowerRequest.SCREEN_STATE_BRIGHT);
            }

            int screenBrightness = mScreenBrightnessSettingDefault;
            float screenAutoBrightnessAdjustment = 0.0f;
            boolean autoBrightness = (mScreenBrightnessModeSetting ==
                    Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
            if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) {
                screenBrightness = mScreenBrightnessOverrideFromWindowManager;
                autoBrightness = false;
            } else if (isValidBrightness(mTemporaryScreenBrightnessSettingOverride)) {
                screenBrightness = mTemporaryScreenBrightnessSettingOverride;
            } else if (isValidBrightness(mScreenBrightnessSetting)) {
                screenBrightness = mScreenBrightnessSetting;
            }
            if (autoBrightness) {
                screenBrightness = mScreenBrightnessSettingDefault;
                if (isValidAutoBrightnessAdjustment(
                        mTemporaryScreenAutoBrightnessAdjustmentSettingOverride)) {
                    screenAutoBrightnessAdjustment =
                            mTemporaryScreenAutoBrightnessAdjustmentSettingOverride;
                } else if (isValidAutoBrightnessAdjustment(
                        mScreenAutoBrightnessAdjustmentSetting)) {
                    screenAutoBrightnessAdjustment = mScreenAutoBrightnessAdjustmentSetting;
                }
            }
            screenBrightness = Math.max(Math.min(screenBrightness,//這個就是最小亮度的調整。
                    mScreenBrightnessSettingMaximum), mScreenBrightnessSettingMinimum);
            screenAutoBrightnessAdjustment = Math.max(Math.min(
                    screenAutoBrightnessAdjustment, 1.0f), -1.0f);
            mDisplayPowerRequest.screenBrightness = screenBrightness;
            mDisplayPowerRequest.screenAutoBrightnessAdjustment =
                    screenAutoBrightnessAdjustment;
            mDisplayPowerRequest.useAutoBrightness = autoBrightness;

            mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked();

            mDisplayPowerRequest.blockScreenOn = mScreenOnBlocker.isHeld();

            mDisplayReady = mDisplayPowerController.requestPowerState(mDisplayPowerRequest,
                    mRequestWaitForNegativeProximity);//最終都是設定到DisplayPowerController
            mRequestWaitForNegativeProximity = false;

二、PowerManagerService設定背光檔案節點

因此我們的選擇還是直接設定背光的檔案節點,況且我們還要事先獲取背光值,用來下次恢復亮度的時候,設定之前的亮度值。

程式碼如下我們再PowerManager中做了兩個介面turnoffBacklight和turnOnBacklight來控制背光,最終通過binder呼叫到PowerManagerService中。

在PowerManagerService中直接通過了寫節點/讀節點的方式,來設定/獲取背光。

    @Override // Binder call
    public void turnOffBacklight() {
        int lightBrightness = getBackLightBrightness();
        if (lightBrightness != 0) {
            mBackLightBrightness = getBackLightBrightness();
            setBackLightBrightness(0);
        }
    }

    @Override // Binder call
    public boolean turnOnBacklight() {
        int lightBrightness = getBackLightBrightness();
        if (lightBrightness == 0) {
            Slog.d(TAG, "turnOnBacklight setBackLightBrightness :" + mBackLightBrightness);
            setBackLightBrightness(mBackLightBrightness);
            return true;
        }
        return false;
    }

    private int getBackLightBrightness() {
        int level = -1;
        File localFile = new File("/sys/class/leds/lcd-backlight/brightness");
        if (!localFile.canRead()) {
            Slog.w(TAG, "/sys/class/leds/lcd-backlight can not read!");
            return level;
        }
        try {
            FileInputStream in = new FileInputStream(localFile);
            byte[] b = new byte[4];
            in.read(b);
            int count = 0;
            for (int i = 0; i < 4; i++) {
                if (b[i] >= '0' && b[i] <= '9') {//非數字去除
                    count++;
                } else {
                    break;
                }
            }
            String str = new String(b, 0, count);
            str = str.replaceAll("\\s+", "");//去除空格,換行符等
            level = Integer.parseInt(str);

            in.close();

        } catch (Exception e) {
        }
        return level;
    }

    private void setBackLightBrightness(int level) {
        File localFile = new File("/sys/class/leds/lcd-backlight/brightness");
        if (!localFile.canWrite()) {
            Slog.w(TAG, "/sys/class/leds/lcd-backlight can not write!");
            return;
        }
        try {
            FileOutputStream fos = new FileOutputStream(localFile);
            fos.write(String.valueOf(level).getBytes());
            fos.close();
        } catch (Exception e) {
        }
    }

我們再Launcher的按鈕點選後呼叫PowerManager中的turnoffBacklight函式,滅屏。

三、恢復螢幕亮度

3.1 按鍵

最後我們在按鍵和觸屏的時候恢復螢幕亮度,普通按鍵的流程會先到PhoneWindowManager的interceptKeyBeforeQueueing函式先處理,我們可以在這個函式中先進行背光亮度的判讀, 部分程式碼如下:

    @Override
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) {
        if (!mSystemBooted) {
            // If we have not yet booted, don't let key events do anything.
            return 0;
        }

        if (mPowerManager.isScreenOn()) {
            if (mPowerManager.turnOnBacklight()) {
                return 0;
            }
        }.......

邏輯無非兩種情況,呼叫mPowerManager.isScreenOn函式

1.如果isScreenOn是返回true的。因為PowerManagerService的流程沒有改變,系統還認為螢幕是亮著的。

所以我們turnOnBacklight,如果這個時候螢幕的亮度為0,代表我們之前呼叫過turnoffBacklight函式把背景光亮度設定為0了,我們把它恢復的亮度,而函式turnOnBacklight返回true,在interceptKeyBeforeQueueing函式中直接return了,代表這次的按鍵就點亮螢幕了,不會走interceptKeyBeforeQueueing的後續流程了,返回0也不會最後傳給使用者了。

如果這個時候有亮度,代表之前沒有呼叫turnoffBacklight,函式turnOnBacklight返回false,繼續interceptKeyBeforeQueueing的原有流程。代表如果螢幕亮著,按鍵處理走自己的流程。

2.如果isScreenOn返回false,代表現在滅屏了。那麼這是PowerManagerService的流程了,和我們的背景光無關。

3.2 觸屏

觸屏的話流程會有一個不一樣,因為在NativeInputManager的interceptMotionBeforeQueueing函式中,只有當isScreenOn是false的時候才會呼叫上層的interceptMotionBeforeQueueingWhenScreenOff函式。也就是滅屏的時候才會到PhoneWindowManager的interceptMotionBeforeQueueingWhenScreenOff函式,而我們恰恰是要在PowerManagerService亮屏的時候,進行我們的邏輯。

void NativeInputManager::interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) {
    // Policy:
    // - Ignore untrusted events and pass them along.
    // - No special filtering for injected events required at this time.
    // - Filter normal events based on screen state.
    // - For normal events brighten (but do not wake) the screen if currently dim.
    if ((policyFlags & POLICY_FLAG_TRUSTED) && !(policyFlags & POLICY_FLAG_INJECTED)) {
        if (isScreenOn()) {
            policyFlags |= POLICY_FLAG_PASS_TO_USER;

            if (!isScreenBright()) {
                policyFlags |= POLICY_FLAG_BRIGHT_HERE;
            }
        } else {
            JNIEnv* env = jniEnv();
            jint wmActions = env->CallIntMethod(mServiceObj,
                        gServiceClassInfo.interceptMotionBeforeQueueingWhenScreenOff,
                        policyFlags);
            if (checkAndClearExceptionFromCallback(env,
                    "interceptMotionBeforeQueueingWhenScreenOff")) {
                wmActions = 0;
            }

            policyFlags |= POLICY_FLAG_WOKE_HERE | POLICY_FLAG_BRIGHT_HERE;
            handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
        }
    } else {
        policyFlags |= POLICY_FLAG_PASS_TO_USER;
    }
}
所以在PhoneWindowManager的interceptMotionBeforeQueueingWhenScreenOff函式處理不行,最後只能到應用程序處理,看我之前幾篇分析按鍵的部落格知道,觸屏事件最終會呼叫到ViewRootImpl的各個InputStage中,而觸屏和按鍵會走到ViewPostImeInputStage中去
    final class ViewPostImeInputStage extends InputStage {
        public ViewPostImeInputStage(InputStage next) {
            super(next);
        }

        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                // If delivering a new non-key event, make sure the window is
                // now allowed to start updating.
                handleDispatchDoneAnimating();
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {//觸屏事件
                    if (mPowerManager.isScreenOn()) {
                        if (mPowerManager.turnOnBacklight()) {
                            return FORWARD;
                        }
                    }
                    return processPointerEvent(q);
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    return processTrackballEvent(q);
                } else {
                    return processGenericMotionEvent(q);
                }
            }
        }
這裡的話流程和按鍵那邊的處理邏輯是一樣的,就不分析了。

四、注意

有一點我們必須注意,手機如果要殺應用的話,輸入法絕對不能殺,殺了輸入法後,會導致輸入法沒有重啟的這段時間,底層按鍵不能分發到上層應用。具體原因,後續分析。