车载上的android4.4系统,基本上常亮。但最近需要一个新功能可以在launcher新增一个按钮,点击的时候。屏幕亮度为0,但实际上不等于按power键,不会睡眠。
然后可以按任意键恢复亮度,包括触屏事件。
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;
因此我们的选择还是直接设置背光的文件节点,况且我们还要事先获取背光值,用来下次恢复亮度的时候,设置之前的亮度值。
代码如下我们再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函数,灭屏。
最后我们在按键和触屏的时候恢复屏幕亮度,普通按键的流程会先到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的流程了,和我们的背景光无关。
触屏的话流程会有一个不一样,因为在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);
}
}
}
这里的话流程和按键那边的处理逻辑是一样的,就不分析了。
有一点我们必须注意,手机如果要杀应用的话,输入法绝对不能杀,杀了输入法后,会导致输入法没有重启的这段时间,底层按键不能分发到上层应用。具体原因,后续分析。