前段時間項目之中遇到了按鍵燈不亮的問題,稍微看了一下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裡面的千萬不能隨意改,不然會出大亂子。