android4.4 车载灭屏 按任意键及触摸屏幕恢复亮屏

来源:互联网 发布:如何用备忘录编程 编辑:程序博客网 时间:2024/04/27 01:33

车载上的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);                }            }        }
这里的话流程和按键那边的处理逻辑是一样的,就不分析了。


四、注意

有一点我们必须注意,手机如果要杀应用的话,输入法绝对不能杀,杀了输入法后,会导致输入法没有重启的这段时间,底层按键不能分发到上层应用。具体原因,后续分析。


1 0