Phone 通話過程中 PSensor 工作流程--
概要
在Android手機通話過程中,使用者將手機靠近/遠離頭部,會導致手機螢幕滅/亮,這實際上是Proximity Sensor在起作用(參考1)。通俗的來講Proximity Sensor就是近距離感測器,後文簡寫為PSensor,近距離感測器可用於測量物體靠近或遠離。根據PSensor的這一特徵,在計數以及自動化控制等領域都有使用近距離感測器(參考2,參考3)。目前,市面上主流的智慧手機均包含了近距離感測器。PSensor檢測到手機被遮擋則關閉螢幕,反之則點亮螢幕,一方面可以省電(LCD是耗電大戶),另一方面可以防止使用者近耳接聽時觸碰到螢幕引發誤操作。
本文主要分析PSensor在整個通話過程中實現螢幕亮滅的控制原理。
ProximitySensor初始化流程
在Android 4.4以後,Phone分為了TeleService和InCallUI兩個部分,通話過程中PSensor的控制由packages/apps/InCallUI/src/com/android/incallui/ProximitySensor.Java負責。在以前釋出的文章《Android 4.4 Kitkat Phone工作流程淺析(七)__來電(MT)響鈴流程》和《Android 4.4 Kitkat Phone工作流程淺析(九)__狀態通知流程分析
圖 1 ProximitySensor初始化流程
InCallUI與TeleService通過兩種方式進行通訊,即Broadcast和AIDL。當MO流程發起時,InCallUI最終會通過廣播的方式將MO請求傳遞給TeleService。而當通話狀態返回以及通話控制命令發起時,InCallUI和TeleService之間將會通過AIDL的方式進行通訊,即CallHandlerService和CallHandlerServiceProxy,以及CallCommandService和CallCommandClient。使用AIDL方式通訊需要建立連線(bindService),而在建立連線的時候對ProximitySensor進行了初始化操作。
1. ProximitySensor初始化是在InCallPresenter中完成;
在InCallPresenter中例項化了ProximitySensor物件並將其新增到Set<InCallStateListener>序列中,當通話狀態變更時回撥ProximitySensor的onStateChanged()方法。
2. ProximitySensor的初始化依賴於InCallUI中CallHandlerService的繫結;
當TeleService通過bindService連線CallHandlerServiceProxy和CallHandlerService時,經過逐步呼叫後完成ProximitySensor的初始化。而bindService的呼叫則依賴於Call State的改變,Call State的改變有兩種情況:incoming 和 update,即當有來電或者通話狀態改變時,會觸發TeleService的bindService操作。
ProximitySensor使用流程
通話狀態變更之後ProximitySensor完成初始化。ProximitySensor.java的關鍵方法如下:
- // 構造方法,完成初始化
- // PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK是@hide的,普通app無法獲取
- public ProximitySensor(Context context, AudioModeProvider audioModeProvider) {
- mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
- mProximityWakeLock = mPowerManager.newWakeLock(
- PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
- } else {
- mProximityWakeLock = null;
- }
- mAccelerometerListener = new AccelerometerListener(context, this);
- mAudioModeProvider = audioModeProvider;
- mAudioModeProvider.addListener(this);
- }
- // 完成善後工作,初始化以及結束通話電話後會被呼叫
- publicvoid tearDown() {
- mAudioModeProvider.removeListener(this);
- mAccelerometerListener.enable(false);
- if (mProximityWakeLock != null && mProximityWakeLock.isHeld()) {
- mProximityWakeLock.release();
- }
- }
- // 當裝置的方向改變之後會執行,用於判斷是否啟用PSensor(注:在滅屏狀態下不會觸發)
- // mOrientation的值包含:
- // ORIENTATION_UNKNOWN = 0
- // ORIENTATION_VERTICAL = 1 垂直襬放
- // ORIENTATION_HORIZONTAL = 2 水平擺放
- @Override
- publicvoid orientationChanged(int orientation) {
- mOrientation = orientation;
- updateProximitySensorMode();
- }
- // 當通話狀態有改變則會通過InCallPresenter回撥
- @Override
- publicvoid onStateChange(InCallState state, CallList callList) {
- // 如果是來電則無須啟用PSensor
- boolean isOffhook = (InCallState.INCALL == state
- || InCallState.OUTGOING == state);
- if (isOffhook != mIsPhoneOffhook) {
- mIsPhoneOffhook = isOffhook;
- mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
- mAccelerometerListener.enable(mIsPhoneOffhook);
- updateProximitySensorMode();
- }
- }
- //... ...省略
- // 當通話過程中Audio模式有變化則執行,包括:
- // 1. 藍芽耳機連線
- // 2. 有線耳機連線
- // 3. 開啟揚聲器
- @Override
- publicvoid onAudioMode(int mode) {
- updateProximitySensorMode();
- }
- // 撥號盤顯示時回撥,此時禁用PSensor以便使用者輸入
- publicvoid onDialpadVisible(boolean visible) {
- mDialpadVisible = visible;
- updateProximitySensorMode();
- }
- // Config改變時回撥,如開啟物理鍵盤(如今許多裝置都已不再配備物理鍵盤)
- publicvoid onConfigurationChanged(Configuration newConfig) {
- mIsHardKeyboardOpen = newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
- // Update the Proximity sensor based on keyboard state
- updateProximitySensorMode();
- }
- // InCallUI顯示或隱藏時回撥
- publicvoid onInCallShowing(boolean showing) {
- if (showing) {
- mUiShowing = true;
- } elseif (mPowerManager.isScreenOn()) {
- mUiShowing = false;
- }
- updateProximitySensorMode();
- }
- /**
- * 根據當前裝置狀態,使用wake lock鎖來控制PSensor的行為
- * On devices that have a proximity sensor, to avoid false touches
- * during a call, we hold a PROXIMITY_SCREEN_OFF_WAKE_LOCK wake lock
- * whenever the phone is off hook. (When held, that wake lock causes
- * the screen to turn off automatically when the sensor detects an
- * object close to the screen.)
- * 以上為google解釋通話過程中PSensor的工作原理,即
- * 在通話過程中持有PROXIMITY_SCREEN_OFF_WAKE_LOCK的wake lock,如果檢測物體靠近
- * 則關閉螢幕以防止使用者誤點選
- *
- * 以下情況將會禁用PSensor的亮滅屏控制,即PSensor無效:
- * 1) 裝置已連線藍芽耳機
- * 2) 裝置已插入有線耳機
- * 3) 裝置開啟揚聲器
- * 4) 裝置開啟物理鍵盤(如今許多裝置都已不再配備物理鍵盤)
- */
- privatevoid updateProximitySensorMode() {
- if (proximitySensorModeEnabled()) {
- synchronized (mProximityWakeLock) {
- finalint audioMode = mAudioModeProvider.getAudioMode();
- // 以下情況將會禁用PSensor,是否禁用需根據update時具體狀態決定:
- // 1. 插入有線耳機
- // 2. 接入藍芽耳機
- // 3. 開啟揚聲器
- // 4. 開啟物理鍵盤(如今許多裝置都已不再配備物理鍵盤)
- // 5. 裝置水平放置
- // screenOnImmediately = true表示禁用PSensor
- // screenOnImmediately = false表示啟用PSensor,此時螢幕的亮滅則根據是否
- // 有物體靠近或遠離PSensor決定,靠近則滅屏,遠離則亮屏
- boolean screenOnImmediately = (AudioMode.WIRED_HEADSET == audioMode
- || AudioMode.SPEAKER == audioMode
- || AudioMode.BLUETOOTH == audioMode
- || mIsHardKeyboardOpen);
- //... ...省略
- // 當裝置水平放置,且InCallActivity不在前臺顯示時,此時!mUiShowing && horizontal = true
- // 從而screenOnImmediately = true,並最終禁用PSensor
- finalboolean horizontal =
- (mOrientation == AccelerometerListener.ORIENTATION_HORIZONTAL);
- screenOnImmediately |= !mUiShowing && horizontal;
- // 當裝置水平放置且開啟撥號盤時,screenOnImmediately = true,禁用PSensor
- screenOnImmediately |= mDialpadVisible && horizontal;
- if (mIsPhoneOffhook && !screenOnImmediately) {
- final String logStr = "turning on proximity sensor: ";
- if (!mProximityWakeLock.isHeld()) {
- Log.i(this, logStr + "acquiring");
- // 如果沒有執行過acquire方法則執行wakelock申請的動作
- // 該方法用於Enable PSensor的亮滅屏控制
- mProximityWakeLock.acquire();
- } else {
- // 無須再次acquire
- Log.i(this, logStr + "already acquired");
- }
- } else {
- final String logStr = "turning off proximity sensor: ";
- if (mProximityWakeLock.isHeld()) {
- Log.i(this, logStr + "releasing");
- // Wait until user has moved the phone away from his head if we are
- // releasing due to the phone call ending.
- // Qtherwise, turn screen on immediately
- // 禁用PSensor的亮滅屏控制包含兩種情況:
- // 第一種:
- // 如果當前裝置不再是OFFHOOK狀態,比如已經結束通話電話即 mIsPhoneOffhook = false
- // 此時screenOnImmediately有可能為false,即裝置還處於滅屏的狀態,這種情況下會根據
- // PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE 這個FLAG來release wake lock
- // 也就是說會立即釋放鎖,但需要等使用者遠離PSensor時才會禁用PSensor並點亮螢幕
- // 第二種:
- // 當update時發現需要禁用PSensor,比如在PSensor被遮擋裝置滅屏的情況下插入有線耳機
- // 此時會導致Audio模式的改變從而呼叫updateProximitySensorMode,同時,在這種情況下
- // screenOnImmediately = true,則會立即禁用PSensor並release wake lock點亮螢幕
- int flags =
- (screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE);
- mProximityWakeLock.release(flags);
- } else {
- Log.i(this, logStr + "already released");
- }
- }
- }
- }
- }
ProximitySensor使用流程小結
1. 在InCallUI中通過ProximitySensor提供的public介面,呼叫updateProximitySensorMode()更新PSensor的Enable/Unenable狀態。
public介面包括:
orientationChanged():裝置方向改變後回撥;
onStateChange():裝置InCallState改變後回撥;
onAudioMode():裝置Audio模式改變後回撥,Audio模式包括連線藍芽耳機,插入有線耳機,開啟揚聲器;
onDialpadVisible():裝置通話時時Dialpad顯示狀態改變後回撥;
onConfigurationChanged():裝置Configuration改變後回撥,如彈出物理鍵盤;
onInCallShowing():裝置InCallActivity顯示狀態改變後回撥;
2. PSensor的控制在ProximitySensor中通過如下方式完成:
①. 例項化PowerManager.WakeLock
- mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
- mProximityWakeLock = mPowerManager.newWakeLock(
- PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
- } else {
- mProximityWakeLock = null;
- }
- mProximityWakeLock.acquire(); // 申請,即Enable PSensor
- if (mProximityWakeLock.isHeld()) {
- int flags =(screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE);
- mProximityWakeLock.release(flags); // 釋放,即Unenable PSensor
- }
PSensor工作流程
在分析了InCallUI中ProximitySensor的初始化流程和控制流程之後,還需具體檢視在整個通話過程中,PSensor是如何完成工作螢幕亮滅控制的。在前文的分析中,ProximitySensor通過mProximityWakeLock.acquire()和mProximityWakeLock.release()來實現PSensor的Enable/Unenable,從而讓PSensor完成螢幕亮滅的控制。整個流程大致分為:WakeLock申請、PSensor關閉/點亮螢幕、系統休眠/喚醒三大部分。
可能有童鞋要問了:WakeLock是什麼?PSensor與WakeLock有什麼關係?申請和釋放WakeLock的作用什麼?後文會為大家一一解釋。
WakeLock
WakeLock也可稱之為喚醒鎖,是Android提供的一種機制,用於阻止裝置進入深度休眠(CPU、Memory掉電參考4 參考5)。當一個應用程式申請了WakeLock後,即使使用者按下Power鍵關閉螢幕,該應用依然可以保持執行,如音樂播放器。
Android提供了五種型別的WakeLock,即PARTIAL_WAKE_LOCK、SCREEN_DIM_WAKE_LOCK、SCREEN_BRIGHT_WAKE_LOCK、FULL_WAKE_LOCK、PROXIMITY_SCREEN_OFF_WAKE_LOCK。其中SCREEN_DIM_WAKE_LOCK、SCREEN_BRIGHT_WAKE_LOCK、FULL_WAKE_LOCK不再建議使用,取而代之的是android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON。PARTIAL_WAKE_LOCK表示裝置CPU 持續運轉,螢幕和鍵盤背光允許關閉,普通應用可以獲取,而PROXIMITY_SCREEN_OFF_WAKE_LOCK則是PSensor的專用,只有系統APP才有權使用。
使用者在通話過程中自然不希望裝置進入深度休眠,因此電話應用(這裡指InCallUI)會申請WakeLock,而這一步實際上在ProximitySensor的updateProximitySensorMode方法中完成,關鍵程式碼如下:
- privatefinal PowerManager mPowerManager;
- privatefinal PowerManager.WakeLock mProximityWakeLock;
- mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
- mProximityWakeLock = mPowerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
- // 申請WakeLock
- mProximityWakeLock.acquire();
- // 釋放WakeLock
- int flags = (screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE);
- mProximityWakeLock.release(flags);
1. PROXIMITY_SCREEN_OFF_WAKE_LOCK在PowerManager定義為@hide因此普通APP無法直接呼叫;
2. 申請WakeLock需要在AndroidManifest.xml新增android.permission.WAKE_LOCK許可權;
3. flags為0表示立即釋放鎖,螢幕會立即點亮。flags為PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE則表示立即釋放鎖,但此時已然Enable PSensor,需要等待物體遠離後再亮屏;
普通應用也可以申請/釋放WakeLock,但PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK只有系統應用才有權申請/釋放,也就是說該型別的WakeLock是為PSensor量身打造的。在acquire/release WakeLock的過程中,實際上也一併完成了PSensor的Enable/Unenable,具體將在下一節給大家解釋。
PSensor關閉/點亮螢幕
本文提及的PSensor關閉/點亮螢幕指的是,Enable PSensor並滿足特定條件(靠近/遠離)後,觸發螢幕關閉/開啟流程(實際上是休眠/喚醒流程,後文解釋),而整個流程分為兩步:註冊PSensor Listener及PSensor狀態觸發亮滅屏。
註冊PSensor Listener
InCallUI中完成WakeLock的申請後,便進入到frameworks/base/core/java/anroid/os/PowerManager.java的acquire方法中,並開啟了WakeLock的處理流程,關鍵程式碼如下:
- publicvoid acquire() {
- //... ...省略
- acquireLocked();
- }
- Private void acquireLocked() {
- // ... ..省略 mService即PowerManagerService物件
- mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource);
- }
- @Override
- publicvoid acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
- WorkSource ws) {
- //... ...省略
- try {
- acquireWakeLockInternal(lock, flags, tag, packageName, ws, uid, pid);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- privatevoid acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName,
- WorkSource ws, int uid, int pid) {
- synchronized (mLock) {
- //... ...省略
- updatePowerStateLocked();
- }
- }
- privatevoid updatePowerStateLocked() {
- //... ...省略
- // 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.
- finallong now = SystemClock.uptimeMillis();
- int dirtyPhase2 = 0;
- for (;;) {
- int dirtyPhase1 = mDirty;
- dirtyPhase2 |= dirtyPhase1;
- mDirty = 0;
- //檢查當前系統中所有啟用的(沒有釋放)WakeLock
- updateWakeLockSummaryLocked(dirtyPhase1);
- //更新主動申請的系統狀態如bright/dim
- updateUserActivitySummaryLocked(now, dirtyPhase1);
- // 如果經過前面的檢查和更新後,沒有新的狀態變更則退出迴圈準備休眠
- if (!updateWakefulnessLocked(dirtyPhase1)) {
- break;
- }
- }
- // Phase 2: Update dreams and display power state.
- // 更新dream屏保狀態
- 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!
- // 休眠/喚醒前最後一步,之後會跳轉到native層執行申請/釋放鎖的操作
- updateSuspendBlockerLocked();
- }
- privatevoid updateDisplayPowerStateLocked(int dirty) {
- //... ...省略
- mDisplayReady = mDisplayPowerController.requestPowerState(mDisplayPowerRequest,
- mRequestWaitForNegativeProximity);
- //... ...省略
- }
- publicboolean requestPowerState(DisplayPowerRequest request,
- boolean waitForNegativeProximity) {
- //... ...省略
- if (changed && !mPendingRequestChangedLocked) {
- mPendingRequestChangedLocked = true;
- sendUpdatePowerStateLocked();
- }
- //... ...省略
- }
- privatevoid updatePowerState() {
- //... ...省略
- setProximitySensorEnabled(true);
- //... ....省略
- }
- privatevoid setProximitySensorEnabled(boolean enable) {
- if (enable) {
- if (!mProximitySensorEnabled) {
- // Register the listener.
- mProximitySensorEnabled = true;
- mSensorManager.registerListener(mProximitySensorListener, mProximitySensor,
- SensorManager.SENSOR_DELAY_NORMAL, mHandler);
- }
- } else {
- if (mProximitySensorEnabled) {
- // Unregister the listener.
- //... ...省略
- mSensorManager.unregisterListener(mProximitySensorListener);
- //... ...省略
- }
- }
- }
整個註冊PSensor Listener流程如圖2所示:
圖 2 PSensor Enable流程
PSensor狀態觸發亮滅屏
當PSensor的狀態發生改變之後便會回撥其Listener的onSensorChanged()方法,並判斷當前遮擋物是靠近還是遠離PSensor:
- privatefinal SensorEventListener mProximitySensorListener = new SensorEventListener() {
- @Override
- publicvoid onSensorChanged(SensorEvent event) {
- if (mProximitySensorEnabled) {
- finallong time = SystemClock.uptimeMillis();
- //從Event中獲取distance值,這個值與driver上報有關,不同裝置有可能不同
- //從當前測試裝置上返回的資料來看,靠近時distance=0.0,遠離時distance=1.0
- finalfloat distance = event.values[0];
- //mProximityThreshold的預設值為5.0f,具體是在driver中設定的
- //靠近:distance = 0.0 -> positive = true -> POSITIVE
- //遠離:distance = 1.0 -> positive = false -> NEGATIVE
- boolean positive = distance >= 0.0f && distance < mProximityThreshold;
- handleProximitySensorEvent(time, positive);
- }
- }