前段时间项目之中遇到了按键灯不亮的问题,稍微看了一下framework的代码,发现是因为滑盖的开关状态对其有影响。
在PhoneWindowManager中有定义:
boolean mLidOpen;
这里没有初始化,所以为false,而且硬件确实没有装滑盖,所以PhoneWindowManager对就不会对mLidOpen进行更新。
void readLidState() {
try {
int sw = mWindowManager.getSwitchState(RawInputEvent.SW_LID); //return -1
if (sw >= 0) {
mLidOpen = sw == 0;
}
} catch (RemoteException e) {
// Ignore
}
}
这里,mLidOpen也不会更新,输入事件不会主动上报。
/** {@inheritDoc} */
public boolean preprocessInputEventTq(RawInputEvent event) {
switch (event.type) {
case RawInputEvent.EV_SW:
if (event.keycode == RawInputEvent.SW_LID) {
// lid changed state
mLidOpen = event.value == 0;
boolean awakeNow = mKeyguardMediator.doLidChangeTq(mLidOpen);
updateRotation(Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE);
// ...省略部分代码
}
}
return false;
}
因此,PhoneWindowManager会调用PowerManagerService的setKeyboardVisibility
/** {@inheritDoc} */
public void adjustConfigurationLw(Configuration config) {
readLidState();
final boolean lidOpen = !KEYBOARD_ALWAYS_HIDDEN && mLidOpen;
mPowerManager.setKeyboardVisibility(lidOpen);
// ...省略部分代码
}
在PowerManagerService中,会根据滑盖状态,决定键盘的可见状态。
public void setKeyboardVisibility(boolean visible) {
synchronized (mLocks) {
if (mKeyboardVisible != visible) {
mKeyboardVisible = visible;
// ...省略部分代码
}
}
}
也就是说,如果滑盖没有打开,键盘是不可见的(用过滑盖手机的都知道)。
这里要命的就是,PowerManagerService会根据键盘的可见状态,来决定亮不亮键盘灯,因此,我们的键盘灯一直是不亮的。
private int applyKeyboardState(int state) {
int brightness = -1;
if (!mKeyboardVisible) {
brightness = 0;
}
// ...省略部分代码
}
现在反思一下,我们要亮的是按键灯,不是键盘灯,现在两个灯等同为一个灯,键盘跟按键还是有区别的吧,android没有考虑到按键灯。所以我们只能把键盘灯当成按键灯使用了。
解决方法看起来很简单,就是回到问题的源头,在PhoneWindowManager中,就把mLidOpen的值初始化为true,这样它就一直会保持在true的状态,相当于滑盖一直是开的,因此键盘就一只是可见的,键盘灯就会亮了。
但是事情远远没有这么简单,自从改了这个以后,问题接踵而至,各种传感器失效,由于传感起也是刚开始调,因此不会不知道是就是因为改了这个值引起的。
1、重力传感器检测到方向有改变时,系统会调用PhoneWindowManager的rotationForOrientationLw方法,继而根据角度更新界面。
public int rotationForOrientationLw(int orientation, int lastRotation,
boolean displayEnabled) {
synchronized (mLock) {
switch (orientation) {
case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
//always return landscape if orientation set to landscape
return mLandscapeRotation;
case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
//always return portrait if orientation set to portrait
return mPortraitRotation;
}
// case for nosensor meaning ignore sensor and consider only lid
// or orientation sensor disabled
//or case.unspecified
if (mLidOpen) {
return mLidOpenRotation;
}
// ...省略部分代码
}
}
这里,因为 mLidOpen一直保持在true状态,因此屏幕旋转角度一直保持在mLidOpenRotation。任凭你怎么转,屏幕就是不旋转。
mLidOpenRotation在frameworks/base/core/res/res/values/config.xml中读取。
因此可以感叹android的逻辑是多么严谨,如果你滑盖一直打开,说明就是有按键操作,这时候在转屏幕,是不合理的。
2、打电话时,如果脸贴近屏幕,接近传感器会将屏幕背光灯熄灭(省电,且防止误操作),这本身也是个很人性化的操作,没想到也会影响。在InCallScreen中
/* package */ void updateProximitySensorMode(Phone.State state) {
if (proximitySensorModeEnabled()) {
synchronized (mProximityWakeLock) {
// turn proximity sensor off and turn screen on immediately if
// we are using a headset, the keyboard is open, or the device
// is being held in a horizontal position.
boolean screenOnImmediately = (isHeadsetPlugged()
|| PhoneUtils.isSpeakerOn(this)
|| ((mBtHandsfree != null) && mBtHandsfree.isAudioOn())
|| mIsHardKeyboardOpen
|| mOrientation != AccelerometerListener.ORIENTATION_VERTICAL);
if (((state == Phone.State.OFFHOOK) || mBeginningCall) && !screenOnImmediately) {
// Phone is in use! Arrange for the screen to turn off
// automatically when the sensor detects a close object.
if (!mProximityWakeLock.isHeld()) {
if (DBG) Log.d(LOG_TAG, "updateProximitySensorMode: acquiring...");
mProximityWakeLock.acquire();
} else {
if (VDBG) Log.d(LOG_TAG, "updateProximitySensorMode: lock already held.");
}
}
}
}
}
这里的mIsHardKeyboardOpen会一直为true,因此就不会去acquire这个wake lock,因此靠近屏幕就不会熄背光灯。值得注意的是还依赖mOrientation,它是竖直方向才aquire,因为只有举起电话之时,才代表接起了电话。
mIsHardKeyboardOpen在onConfigurationChanged得到赋值
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
mIsHardKeyboardOpen = true;
} else {
mIsHardKeyboardOpen = false;
}
// Update the Proximity sensor based on keyboard state
updateProximitySensorMode();
super.onConfigurationChanged(newConfig);
}
因此,应用层根据Configuration中的hardKeyboardHidden来判断键盘是否可见。
这个值是在PhoneWindowManager中决定的,
/** {@inheritDoc} */
public void adjustConfigurationLw(Configuration config) {
readLidState();
final boolean lidOpen = !KEYBOARD_ALWAYS_HIDDEN && mLidOpen;
mPowerManager.setKeyboardVisibility(lidOpen);
config.hardKeyboardHidden = determineHiddenState(lidOpen,
mLidKeyboardAccessibility, Configuration.HARDKEYBOARDHIDDEN_YES,
Configuration.HARDKEYBOARDHIDDEN_NO);
config.navigationHidden = determineHiddenState(lidOpen,
mLidNavigationAccessibility, Configuration.NAVIGATIONHIDDEN_YES,
Configuration.NAVIGATIONHIDDEN_NO);
config.keyboardHidden = (config.hardKeyboardHidden
== Configuration.HARDKEYBOARDHIDDEN_NO || mHasSoftInput)
? Configuration.KEYBOARDHIDDEN_NO
: Configuration.KEYBOARDHIDDEN_YES;
}
因此,PowerManagerService可能跟Configuration针对键盘状态可能不同,取决于determineHiddenState的返回值,determineHiddenState综合了根据滑盖状态,和mLidKeyboardAccessibility,来决定hardKeyboardHidden的状态,mLidKeyboardAccessibility从config.xml中读取
<!-- Indicate whether the lid state impacts the accessibility of
the physical keyboard. 0 means it doesn't, 1 means it is accessible
when the lid is open, 2 means it is accessible when the lid is
closed. The default is 1. -->
<integer name="config_lidKeyboardAccessibility">1</integer>
再看determineHiddenState,意思滑盖关闭的时候,如果mLidKeyboardAccessibility为1,代表键盘同时不可见。
private int determineHiddenState(boolean lidOpen,
int mode, int hiddenValue, int visibleValue) {
switch (mode) {
case 1:
return lidOpen ? visibleValue : hiddenValue;
case 2:
return lidOpen ? hiddenValue : visibleValue;
}
return visibleValue;
}
这些写出来很容易,其实定位问题花了我好长时间,因此吸取一个教训,framework里面的千万不能随意改,不然会出大乱子。