現在的電子產品越來越人性化,用戶友好化,在給用戶帶來全新體驗的同時,也在改變著人們的日常生活,所以說科技是偉大的,創新是偉大的。
隨著移動設備的多元化發展,各種微型芯片的嵌入,使得它的功能越來越強大。比如各種各樣的Sensor,最常見的一種是Proximity Sensor,現在的品牌機幾乎都具備,也就是在打電話的時候,為了避免誤操作,在電話接近耳朵的時候讓手機處於滅屏狀態,要實現這一功能使用Proximity Sensor是再好不過的了。
但是也有一些設備不具備Proximity Sensor(比如我們的平板設備-_-,因為其主要功能並非打電話,所以沒有添加接近傳感器),為了做到用戶友好化,就必須得在沒有傳感器的狀況下添加自動滅屏功能。
首先找到撥打電話的界面,4.4和之前的系統代碼架構有了很大的改變,之前的撥號程序就是Phone,現在Phone基本上廢掉了,而且之前提供了一個叫void setPokeLock(int pokey, IBinder lock, String tag)的方法,可以實現幾秒後滅屏,還比較好用,之後的系統這個方法給刪掉了。但是加了個Telephony的程序,代碼路徑packages/service/Telephony,按下撥打電話的按鈕後,經過一系列的流程轉換,最終會進入到PhoneGlobals.java中,代碼路徑:packages/services/Telephony/src/com/android/phone,其中有這麼個方法:
/* package */ void updateWakeState() { PhoneConstants.State state = mCM.getState(); // True if the speakerphone is in use. (If so, we *always* use // the default timeout. Since the user is obviously not holding // the phone up to his/her face, we don't need to worry about // false touches, and thus don't need to turn the screen off so // aggressively.) // Note that we need to make a fresh call to this method any // time the speaker state changes. (That happens in // PhoneUtils.turnOnSpeaker().) boolean isSpeakerInUse = (state == PhoneConstants.State.OFFHOOK) && PhoneUtils.isSpeakerOn(this); // TODO (bug 1440854): The screen timeout *might* also need to // depend on the bluetooth state, but this isn't as clear-cut as // the speaker state (since while using BT it's common for the // user to put the phone straight into a pocket, in which case the // timeout should probably still be short.) // Decide whether to force the screen on or not. // // Force the screen to be on if the phone is ringing or dialing, // or if we're displaying the "Call ended" UI for a connection in // the "disconnected" state. // However, if the phone is disconnected while the user is in the // middle of selecting a quick response message, we should not force // the screen to be on. // boolean isRinging = (state == PhoneConstants.State.RINGING); boolean isDialing = (phone.getForegroundCall().getState() == Call.State.DIALING); boolean isVideoCallActive = PhoneUtils.isImsVideoCallActive(mCM.getActiveFgCall()); boolean keepScreenOn = isRinging || isDialing || isVideoCallActive; // keepScreenOn == true means we'll hold a full wake lock: requestWakeState(keepScreenOn ? WakeState.FULL : WakeState.SLEEP); }
/* package */ void requestWakeState(WakeState ws) { if (VDBG) Log.d(LOG_TAG, "requestWakeState(" + ws + ")..."); synchronized (this) { if (mWakeState != ws) { switch (ws) { case PARTIAL: // acquire the processor wake lock, and release the FULL // lock if it is being held. mPartialWakeLock.acquire(); if (mWakeLock.isHeld()) { mWakeLock.release(); } break; case FULL: // acquire the full wake lock, and release the PARTIAL // lock if it is being held. mWakeLock.acquire(); if (mPartialWakeLock.isHeld()) { mPartialWakeLock.release(); } break; case SLEEP: default: // release both the PARTIAL and FULL locks. if (mWakeLock.isHeld()) { mWakeLock.release(); } if (mPartialWakeLock.isHeld()) { mPartialWakeLock.release(); } break; } mWakeState = ws; } } }這個updateWakestate就是更新通話狀態中屏幕的狀態的,如果有Proximity Sensor,會使用WakeLock鎖去更新屏幕狀態,WakeLock定義在PowerManager中,是一個內部類,這個類主要是通過申請和釋放一個鎖來控制屏幕的變化,最終還是調用到PowerManagerService中,PowerManagerService就是我們通常所說的Android電源管理類。
沒有Proximity Sensor的狀態下,keepScreenOn的狀態最終會被置為false,也就進入到case SLEEP這個分支,什麼也不干,這個時候,PowerManagerService就會調用Settings中用戶設置的休眠時間去使屏幕休眠,如果設置中設置的是30分鐘,那你撥打一個電話後30分鐘後才會休眠。
最簡單的使屏幕休眠的方法是goToSleep(long time),PowerManager中提供了接口以供調用,我們只需要獲得PowerManager服務,就可以調用,當然在AndroidManifest中需要添加相應的權限。但是在撥號中這個方法是不可行的,會造成很不好的用戶體驗,原因就不多說了,直接說我改後測試成功的方法。
在PowerManagerService中,定義了一個標誌位mDirty,其中有十二種狀態的變化,通過各種邏輯處理和複雜的判斷,最終達到管理電源的目的,由於這個電源管理框架是google直接維護的,代碼寫的精簡而富於活力,其中的繁多的狀態變化實在是跟的眼花繚亂,我實在無心去慢慢的打Log一步步的跟進流程,但是也有牛人把這套東西一步步跟的清清楚楚並分享出來了,參考:http://wenku.baidu.com/link?url=Ph3fYPtSmbOFpNAvgNIvLJkbo7SW7XWMuRsgLQ0640wPTvXo0DdfIHcXqHpRDN5JHrQb7saiKjAgFvS1Q4kHYqosnze97mIi3iFJjTefS3W
有興趣的可以去深入瞭解。
繼續講我的,在我看到了一個叫private long mUserActivityTimeoutOverrideFromWindowManager = -1;的變量時,我覺得離解決這個問題不遠了。這個官方還有註釋,大概意思是,可通過應用程序設置這個值,臨時的調整屏幕的亮度,-1為禁用。繼續看這個變量在什麼地方用了:
private void setUserActivityTimeoutOverrideFromWindowManagerInternal(long timeoutMillis) { synchronized (mLock) { if (mUserActivityTimeoutOverrideFromWindowManager != timeoutMillis) { mUserActivityTimeoutOverrideFromWindowManager = timeoutMillis; mDirty |= DIRTY_SETTINGS; updatePowerStateLocked(); } } }
設置一個值後調用updatePowerStateLocked方法,這個方法是PowerManagerService的關鍵所在。
private void updatePowerStateLocked() { if (!mSystemReady || mDirty == 0) { return; } // Phase 0: Basic state updates. updateIsPoweredLocked(mDirty); updateStayOnLocked(mDirty); // Phase 1: Update wakefulness. // Loop because the wake lock and user activity computations are influenced // by changes in wakefulness. final long now = SystemClock.uptimeMillis(); int dirtyPhase2 = 0; for (;;) { int dirtyPhase1 = mDirty; dirtyPhase2 |= dirtyPhase1; mDirty = 0; updateWakeLockSummaryLocked(dirtyPhase1); updateUserActivitySummaryLocked(now, dirtyPhase1); if (!updateWakefulnessLocked(dirtyPhase1)) { break; } } // Phase 2: Update dreams and display power state. updateDreamLocked(dirtyPhase2); updateDisplayPowerStateLocked(dirtyPhase2); // Phase 3: Send notifications, if needed. if (mDisplayReady) { sendPendingNotificationsLocked(); } // Phase 4: Update suspend blocker. // Because we might release the last suspend blocker here, we need to make sure // we finished everything else first! updateSuspendBlockerLocked(); }
關於這個方法所作的工作,前面給的連接裡面解釋的很清楚,我也就不多說了,我們需要知道的是屬於用戶的操作而使電源狀態發生更改一定會調用updateUserActivitySummaryLocked(now, dirtyPhase1)方法,比如你觸摸了屏幕,點擊了一個Button等等,都會調用此方法,用以改變電源的狀態,使你的屏幕亮起來,不進入滅屏狀態(已經處於滅屏狀態只能通過按power鍵喚醒)。
那麼,看看updateUserActivitySummaryLocked幹了什麼:
private void updateUserActivitySummaryLocked(long now, int dirty) { // Update the status of the user activity timeout timer. if ((dirty & (DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) != 0) { mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT); long nextTimeout = 0; if (mWakefulness != WAKEFULNESS_ASLEEP) { final int screenOffTimeout = getScreenOffTimeoutLocked(); final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout); mUserActivitySummary = 0; if (mLastUserActivityTime >= mLastWakeTime) { nextTimeout = mLastUserActivityTime + screenOffTimeout - screenDimDuration; if (now < nextTimeout) { mUserActivitySummary |= USER_ACTIVITY_SCREEN_BRIGHT; } else { nextTimeout = mLastUserActivityTime + screenOffTimeout; if (now < nextTimeout) { mUserActivitySummary |= USER_ACTIVITY_SCREEN_DIM; } } } if (mUserActivitySummary == 0 && mLastUserActivityTimeNoChangeLights >= mLastWakeTime) { nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout; if (now < nextTimeout && mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) { mUserActivitySummary = mDisplayPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_BRIGHT ? USER_ACTIVITY_SCREEN_BRIGHT : USER_ACTIVITY_SCREEN_DIM; } } if (mUserActivitySummary != 0) { Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY_TIMEOUT); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextTimeout); } } else { mUserActivitySummary = 0; } if (DEBUG_SPEW) { Slog.d(TAG, "updateUserActivitySummaryLocked: mWakefulness=" + wakefulnessToString(mWakefulness) + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary) + ", nextTimeout=" + TimeUtils.formatUptime(nextTimeout)); } } }
其中有final int screenOffTimeout = getScreenOffTimeoutLocked(); final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);還有一個變量nextTimeout,這個變量就是控制下次滅屏時間的,大致是等於screenOffTimeout 減去screenDimDuration的值,getScreenOffTimeoutLocked() 和 getScreenDimDurationLocked(screenOffTimeout)是什麼呢?看代碼:
private int getScreenOffTimeoutLocked() { int timeout = mScreenOffTimeoutSetting; if (isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) { timeout = Math.min(timeout, mMaximumScreenOffTimeoutFromDeviceAdmin); } if (mUserActivityTimeoutOverrideFromWindowManager >= 0) { timeout = (int)Math.min(timeout, mUserActivityTimeoutOverrideFromWindowManager); } return Math.max(timeout, MINIMUM_SCREEN_OFF_TIMEOUT); } private int getScreenDimDurationLocked(int screenOffTimeout) { return Math.min(SCREEN_DIM_DURATION, (int)(screenOffTimeout * MAXIMUM_SCREEN_DIM_RATIO)); }
getScreenOffTimeoutLocked()的返回值是settings中用戶設置的休眠值,而此時getScreenDimDurationLocked返回的是SCREEN_DIM_DURATION,也是個常量值,7 * 1000,7秒。每次當用戶觸摸屏幕(或者其它的操作),都會重新設置此值,比如你設置的屏幕休眠時間為5分鐘,你沒有對你的設備進行任何操作,在4分59秒的時候觸摸了一下屏幕,那麼你的設備休眠時間又會重新計時,無操作五分鐘後滅屏,如此循環下去,就是在這個地方處理的。
只有一種情況例外(這裡指的是自然狀態下,沒有外來力量參與的情況,比如有了Proximity Sensor,那就是另外一回事了),那就是設備重啟後進入鎖屏狀態時,會調用setUserActivityTimeoutOverrideFromWindowManager方法,將mUserActivityTimeoutOverrideFromWindowManager的值設為10000,也就是10秒,這個時候getScreenOffTimeoutLocked的返回值就不再是Settings中設置的休眠時間,而是10000,getScreenDimDurationLocked的返回值也不是SCREEN_DIM_DURATION,而是screenOffTimeout * MAXIMUM_SCREEN_DIM_RATIO,也就是2000,那麼進入updateUserActivitySummaryLocked方法中計算出來的結果就是8秒滅屏,這也是為什麼在鎖屏狀態下我們的設備會很快的進入休眠的原因。
在鎖屏狀態解除的時候,會在WindowManagerService中調用mPowerManager.setUserActivityTimeoutOverrideFromWindowManager(
mInnerFields.mUserActivityTimeout);
mInnerFields.mUserActivityTimeout);
在這裡將mUserActivityTimeoutOverrideFromWindowManager值設回-1,此時,設備的休眠時間又會回覆到Settings中設置的時間。
看到這裡我們就可以仿照這套流程添加一個變量來控制撥打電話的時候的電源狀態。 在PowerManagerService中加入:
private long mUserActivityTimeoutOverrideFromCall = -1;
在getScreenOffTimeoutLocked()方法中加入:
if (mUserActivityTimeoutOverrideFromCall >= 0) {
timeout = (int)Math.min(timeout, mUserActivityTimeoutOverrideFromCall);
}
timeout = (int)Math.min(timeout, mUserActivityTimeoutOverrideFromCall);
}
還需要加入一個接口以供外部調用:
// Add liao --2015-01-23 private void setUserActivityTimeoutOverrideFromCallInternal(long timeoutMillis) { synchronized (mLock) { if (mUserActivityTimeoutOverrideFromCall != timeoutMillis) { mUserActivityTimeoutOverrideFromCall = timeoutMillis; mDirty |= DIRTY_SETTINGS; updatePowerStateLocked(); } } } // Add liao --2015-01-23 public void setTimeout(long timeoutMillis) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); final long ident = Binder.clearCallingIdentity(); try { setUserActivityTimeoutOverrideFromCallInternal(timeoutMillis); } finally { Binder.restoreCallingIdentity(ident); } }
好,PowerManagerService中的東東加好了,再來PowerManager中加入setTimeout接口,讓外部類可以調用:
public static final long POKE_LOCK_SHORT_TIMEOUT = 1000L; public static final long POKE_LOCK_TIMEOUT_STOP = -1L;
public void setTimeout(long timeout) { try { mService.setTimeout(timeout); } catch (RemoteException e) { } }
當然,別忘記aidl,還需要在IPowerManager.aidl中加入 void setTimeout(long timeout);
對於aidl怎麼在android系統中使用可以參考我前面寫的文章。
對外的接口都添加完了,接下來就是撥號的地方去更改mUserActivityTimeoutOverrideFromCall的值了,在PhoneGlobals的updateWakeState()方法中加入:
mPowerManager.setTimeout(PowerManager.POKE_LOCK_SHORT_TIMEOUT); if (state == PhoneConstants.State.IDLE) { mPowerManager.setTimeout(PowerManager.POKE_LOCK_TIMEOUT_STOP); }
這個方法一開始就執行了這一句:PhoneConstants.State state = mCM.getState();這是獲取電話的狀態,是在撥號中,還是撥通了,還是掛斷了沒有活動,在PhoneConstants中分別定義了這三種狀態:
public enum State { IDLE, RINGING, OFFHOOK; };
當電話撥打的時候,就會調用setTimeout進入到PowerManagerService中的setUserActivityTimeoutOverrideFromCallInternal方法,更新mUserActivityTimeoutOverrideFromCall的值,並且調用updatePowerStateLocked(),從而進入到updateUserActivitySummaryLocked方法中,通過一系列的算法,最終得出的值和鎖屏狀態的滅屏時間是一樣的,大概8秒,如果嫌8秒太長,可以將MINIMUM_SCREEN_OFF_TIMEOUT這個常量改小一點,比如改成7 * 1000,那麼最終得出的滅屏時間就是5秒,撥號後3秒屏幕變暗,5秒滅屏。
其中,IDLE就表示沒有撥號,電話掛斷的時候必然會將狀態改為IDLE,所以在電話掛斷的時候在將mUserActivityTimeoutOverrideFromCall的值設置為-1,讓滅屏時間回覆到Settings中設置的時間。至此,讓沒有Proximity Sensor的設備在通話過程中自動滅屏的功能告一段落。
以上就介紹了Android4.4 無Proximity Sensor的設備撥號中實現自動滅屏,包括了方面的內容,希望對Android開發有興趣的朋友有所幫助。
沒有留言:
張貼留言