1. 程式人生 > >Phone 通話過程中 PSensor 工作流程--不全

Phone 通話過程中 PSensor 工作流程--不全

概要

       在Android手機通話過程中,使用者將手機靠近/遠離頭部,會導致手機螢幕滅/亮,這實際上是Proximity Sensor在起作用(參考1)。通俗的來講Proximity Sensor就是近距離感測器,後文簡寫為PSensor,近距離感測器可用於測量物體靠近或遠離。根據PSensor的這一特徵,在計數以及自動化控制等領域都有使用近距離感測器(參考2參考3)。目前,市面上主流的智慧手機均包含了近距離感測器。PSensor檢測到手機被遮擋則關閉螢幕,反之則點亮螢幕,一方面可以省電(LCD是耗電大戶),另一方面可以防止使用者近耳接聽時觸碰到螢幕引發誤操作。

       本文主要分析PSensor在整個通話過程中實現螢幕亮滅的控制原理。


本文來自https://blog.csdn.net/daiqiquan/article/details/51907204

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工作流程淺析(九)__狀態通知流程分析》中已經分析通話狀態變更流程 ( Modem->RILC->RILJ->Framework->Telephony->InCallUI ) ,而TeleService與InCallUI建立連線是在通話狀態變更之後。因此ProximitySensor的初始化建立在通話狀態第一次變更後,整個流程如圖1所示:


圖 1 ProximitySensor初始化流程

       InCallUI與TeleService通過兩種方式進行通訊,即Broadcast和AIDL。當MO流程發起時,InCallUI最終會通過廣播的方式將MO請求傳遞給TeleService。而當通話狀態返回以及通話控制命令發起時,InCallUI和TeleService之間將會通過AIDL的方式進行通訊,即CallHandlerService和CallHandlerServiceProxy,以及CallCommandService和CallCommandClient。使用AIDL方式通訊需要建立連線(bindService),而在建立連線的時候對ProximitySensor進行了初始化操作。

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的關鍵方法如下:

[java]  view plain  copy   在CODE上檢視程式碼片 派生到我的程式碼片
  1. // 構造方法,完成初始化  
  2. // PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK是@hide的,普通app無法獲取  
  3. public ProximitySensor(Context context, AudioModeProvider audioModeProvider) {  
  4.     mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);  
  5.     if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {  
  6.         mProximityWakeLock = mPowerManager.newWakeLock(  
  7.                 PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);  
  8.     } else {  
  9.         mProximityWakeLock = null;  
  10.     }  
  11.     mAccelerometerListener = new AccelerometerListener(context, this);  
  12.     mAudioModeProvider = audioModeProvider;  
  13.     mAudioModeProvider.addListener(this);  
  14. }  
  15.   
  16. // 完成善後工作,初始化以及結束通話電話後會被呼叫  
  17. public void tearDown() {  
  18.     mAudioModeProvider.removeListener(this);  
  19.     mAccelerometerListener.enable(false);  
  20.     if (mProximityWakeLock != null && mProximityWakeLock.isHeld()) {  
  21.         mProximityWakeLock.release();  
  22.     }  
  23. }  
  24.   
  25. // 當裝置的方向改變之後會執行,用於判斷是否啟用PSensor(注:在滅屏狀態下不會觸發)  
  26. // mOrientation的值包含:  
  27. // ORIENTATION_UNKNOWN = 0  
  28. // ORIENTATION_VERTICAL = 1   垂直襬放  
  29. // ORIENTATION_HORIZONTAL = 2 水平擺放  
  30. @Override  
  31. public void orientationChanged(int orientation) {  
  32.     mOrientation = orientation;  
  33.     updateProximitySensorMode();  
  34. }  
  35.   
  36. // 當通話狀態有改變則會通過InCallPresenter回撥  
  37. @Override  
  38. public void onStateChange(InCallState state, CallList callList) {  
  39.     // 如果是來電則無須啟用PSensor  
  40.     boolean isOffhook = (InCallState.INCALL == state  
  41.             || InCallState.OUTGOING == state);  
  42.     if (isOffhook != mIsPhoneOffhook) {  
  43.         mIsPhoneOffhook = isOffhook;  
  44.         mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;  
  45.         mAccelerometerListener.enable(mIsPhoneOffhook);  
  46.         updateProximitySensorMode();  
  47.     }  
  48. }  
  49.   
  50. //... ...省略  
  51.   
  52. // 當通話過程中Audio模式有變化則執行,包括:  
  53. // 1. 藍芽耳機連線  
  54. // 2. 有線耳機連線  
  55. // 3. 開啟揚聲器  
  56. @Override  
  57. public void onAudioMode(int mode) {  
  58.     updateProximitySensorMode();  
  59. }  
  60.   
  61. // 撥號盤顯示時回撥,此時禁用PSensor以便使用者輸入  
  62. public void onDialpadVisible(boolean visible) {  
  63.     mDialpadVisible = visible;  
  64.     updateProximitySensorMode();  
  65. }  
  66.   
  67. // Config改變時回撥,如開啟物理鍵盤(如今許多裝置都已不再配備物理鍵盤)  
  68. public void onConfigurationChanged(Configuration newConfig) {  
  69.     mIsHardKeyboardOpen = newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;  
  70.     // Update the Proximity sensor based on keyboard state  
  71.     updateProximitySensorMode();  
  72. }  
  73.   
  74. // InCallUI顯示或隱藏時回撥  
  75. public void onInCallShowing(boolean showing) {  
  76.     if (showing) {  
  77.         mUiShowing = true;  
  78.     } else if (mPowerManager.isScreenOn()) {  
  79.         mUiShowing = false;  
  80.     }  
  81.     updateProximitySensorMode();  
  82. }  
通過檢視不難發現,以上方法(除了構造方法以外)均呼叫了updateProximitySensorMode方法,整個通話過程中正是由其觸發PSensor的控制,關鍵程式碼如下: [java]  view plain  copy   在CODE上檢視程式碼片 派生到我的程式碼片
  1. /** 
  2.  * 根據當前裝置狀態,使用wake lock鎖來控制PSensor的行為 
  3.  * On devices that have a proximity sensor, to avoid false touches 
  4.  * during a call, we hold a PROXIMITY_SCREEN_OFF_WAKE_LOCK wake lock 
  5.  * whenever the phone is off hook.  (When held, that wake lock causes 
  6.  * the screen to turn off automatically when the sensor detects an 
  7.  * object close to the screen.) 
  8.  * 以上為google解釋通話過程中PSensor的工作原理,即 
  9.  * 在通話過程中持有PROXIMITY_SCREEN_OFF_WAKE_LOCK的wake lock,如果檢測物體靠近 
  10.  * 則關閉螢幕以防止使用者誤點選 
  11.  * 
  12.  * 以下情況將會禁用PSensor的亮滅屏控制,即PSensor無效: 
  13.  * 1) 裝置已連線藍芽耳機 
  14.  * 2) 裝置已插入有線耳機 
  15.  * 3) 裝置開啟揚聲器 
  16.  * 4) 裝置開啟物理鍵盤(如今許多裝置都已不再配備物理鍵盤) 
  17.  */  
  18. private void updateProximitySensorMode() {  
  19.     if (proximitySensorModeEnabled()) {  
  20.         synchronized (mProximityWakeLock) {  
  21.             final int audioMode = mAudioModeProvider.getAudioMode();  
  22.             // 以下情況將會禁用PSensor,是否禁用需根據update時具體狀態決定:  
  23.             // 1. 插入有線耳機  
  24.             // 2. 接入藍芽耳機  
  25.             // 3. 開啟揚聲器  
  26.             // 4. 開啟物理鍵盤(如今許多裝置都已不再配備物理鍵盤)  
  27.             // 5. 裝置水平放置  
  28.             // screenOnImmediately = true表示禁用PSensor  
  29.             // screenOnImmediately = false表示啟用PSensor,此時螢幕的亮滅則根據是否  
  30.             // 有物體靠近或遠離PSensor決定,靠近則滅屏,遠離則亮屏  
  31.             boolean screenOnImmediately = (AudioMode.WIRED_HEADSET == audioMode  
  32.                     || AudioMode.SPEAKER == audioMode  
  33.                     || AudioMode.BLUETOOTH == audioMode  
  34.                     || mIsHardKeyboardOpen);  
  35.             //... ...省略  
  36.             // 當裝置水平放置,且InCallActivity不在前臺顯示時,此時!mUiShowing && horizontal = true  
  37.             // 從而screenOnImmediately = true,並最終禁用PSensor  
  38.             final boolean horizontal =  
  39.                     (mOrientation == AccelerometerListener.ORIENTATION_HORIZONTAL);  
  40.             screenOnImmediately |= !mUiShowing && horizontal;  
  41.   
  42.             // 當裝置水平放置且開啟撥號盤時,screenOnImmediately = true,禁用PSensor  
  43.             screenOnImmediately |= mDialpadVisible && horizontal;  
  44.             if (mIsPhoneOffhook && !screenOnImmediately) {  
  45.                 final String logStr = "turning on proximity sensor: ";  
  46.                 if (!mProximityWakeLock.isHeld()) {  
  47.                     Log.i(this, logStr + "acquiring");  
  48.                     // 如果沒有執行過acquire方法則執行wakelock申請的動作  
  49.                     // 該方法用於Enable PSensor的亮滅屏控制  
  50.                     mProximityWakeLock.acquire();  
  51.                 } else {  
  52.                     // 無須再次acquire  
  53.                     Log.i(this, logStr + "already acquired");  
  54.                 }  
  55.             } else {  
  56.                 final String logStr = "turning off proximity sensor: ";  
  57.                 if (mProximityWakeLock.isHeld()) {  
  58.                     Log.i(this, logStr + "releasing");  
  59.                     // Wait until user has moved the phone away from his head if we are  
  60.                     // releasing due to the phone call ending.  
  61.                     // Qtherwise, turn screen on immediately  
  62.                     // 禁用PSensor的亮滅屏控制包含兩種情況:  
  63.                     // 第一種:  
  64.                     // 如果當前裝置不再是OFFHOOK狀態,比如已經結束通話電話即 mIsPhoneOffhook = false  
  65.                     // 此時screenOnImmediately有可能為false,即裝置還處於滅屏的狀態,這種情況下會根據  
  66.                     // PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE 這個FLAG來release wake lock  
  67.                     // 也就是說會立即釋放鎖,但需要等使用者遠離PSensor時才會禁用PSensor並點亮螢幕  
  68.                     // 第二種:  
  69.                     // 當update時發現需要禁用PSensor,比如在PSensor被遮擋裝置滅屏的情況下插入有線耳機  
  70.                     // 此時會導致Audio模式的改變從而呼叫updateProximitySensorMode,同時,在這種情況下  
  71.                     // screenOnImmediately = true,則會立即禁用PSensor並release wake lock點亮螢幕  
  72.                     int flags =  
  73.                         (screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE);  
  74.                     mProximityWakeLock.release(flags);  
  75.                 } else {  
  76.                     Log.i(this, logStr + "already released");  
  77.                 }  
  78.             }  
  79.         }  
  80.     }  
  81. }  
通過以上程式碼的分析可以知道,螢幕的亮滅有多種情況。在InCallUI中通過mProximityWakeLock.acquire()和mProximityWakeLock.release()申請/釋放wake lock來 Enable/Unenable PSensor,從而讓PSensor來控制螢幕的亮滅。從這一點也可以看出,通話過程中螢幕的亮滅的控制,實際上與Telephony沒有多大關係。

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

[java]  view plain  copy   在CODE上檢視程式碼片 派生到我的程式碼片
  1. mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);  
  2. if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {  
  3.     mProximityWakeLock = mPowerManager.newWakeLock(  
  4.             PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);  
  5. else {  
  6.     mProximityWakeLock = null;  
  7. }  
②. 通過WakeLock的acquire和release方法Enable/Unenable PSensor [java]  view plain  copy   在CODE上檢視程式碼片 派生到我的程式碼片
  1. mProximityWakeLock.acquire(); // 申請,即Enable PSensor  
  2. if (mProximityWakeLock.isHeld()) {  
  3.     int flags =(screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE);  
  4.     mProximityWakeLock.release(flags); // 釋放,即Unenable PSensor  
  5. }  

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方法中完成,關鍵程式碼如下:

[java]  view plain  copy   在CODE上檢視程式碼片 派生到我的程式碼片
  1. private final PowerManager mPowerManager;  
  2. private final PowerManager.WakeLock mProximityWakeLock;  
  3.   
  4. mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);  
  5. if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {  
  6.     mProximityWakeLock = mPowerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);  
  7.     // 申請WakeLock  
  8.     mProximityWakeLock.acquire();  
  9.     // 釋放WakeLock  
  10.     int flags = (screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE);  
  11.     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的處理流程,關鍵程式碼如下:

[java]  view plain  copy   在CODE上檢視程式碼片 派生到我的程式碼片
  1. public void acquire() {  
  2.     //... ...省略  
  3.     acquireLocked();  
  4. }  
  5. Private void acquireLocked() {  
  6.     // ... ..省略 mService即PowerManagerService物件  
  7.     mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource);  
  8. }  
對於上層應用來說PowerManager實際上只是一個介面,真正實現在frameworks/base/services/java/com/android/server/power/PowerManagerService.java中: [java]  view plain  copy   在CODE上檢視程式碼片 派生到我的程式碼片
  1. @Override  
  2. public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,  
  3.         WorkSource ws) {  
  4.     //... ...省略  
  5.     try {  
  6.         acquireWakeLockInternal(lock, flags, tag, packageName, ws, uid, pid);  
  7.     } finally {  
  8.         Binder.restoreCallingIdentity(ident);  
  9.     }  
  10. }  
  11.   
  12. private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName,  
  13.         WorkSource ws, int uid, int pid) {  
  14.     synchronized (mLock) {  
  15.         //... ...省略  
  16.         updatePowerStateLocked();  
  17.     }  
  18. }  
因為在本節中我們主要關心關閉/點亮螢幕的操作,期間省略了很多狀態更新以及許可權檢查的程式碼。在acquireWakeLockInternal最後呼叫了updatePowerStateLocked,該方法是PowerManagerService中最為重要的方法,也即Android的休眠/喚醒入口方法。其中具有具有5大關鍵步驟,核心程式碼如下: [java]  view plain  copy   在CODE上檢視程式碼片 派生到我的程式碼片
  1. private void updatePowerStateLocked() {  
  2.     //... ...省略  
  3.   
  4.     // Phase 0: Basic state updates.  
  5.     // 基本狀態更新  
  6.     updateIsPoweredLocked(mDirty);  
  7.     updateStayOnLocked(mDirty); // 是否有開啟"充電保持喚醒"功能  
  8.   
  9.     // Phase 1: Update wakefulness.  
  10.     // Loop because the wake lock and user activity computations are influenced  
  11.     // by changes in wakefulness.  
  12.     final long now = SystemClock.uptimeMillis();  
  13.     int dirtyPhase2 = 0;  
  14.     for (;;) {  
  15.         int dirtyPhase1 = mDirty;  
  16.         dirtyPhase2 |= dirtyPhase1;  
  17.         mDirty = 0;  
  18.         //檢查當前系統中所有啟用的(沒有釋放)WakeLock  
  19.         updateWakeLockSummaryLocked(dirtyPhase1);  
  20.         //更新主動申請的系統狀態如bright/dim  
  21.         updateUserActivitySummaryLocked(now, dirtyPhase1);  
  22.         // 如果經過前面的檢查和更新後,沒有新的狀態變更則退出迴圈準備休眠  
  23.         if (!updateWakefulnessLocked(dirtyPhase1)) {  
  24.             break;  
  25.         }  
  26.     }  
  27.   
  28.     // Phase 2: Update dreams and display power state.  
  29.     // 更新dream屏保狀態  
  30.     updateDreamLocked(dirtyPhase2);  
  31.     // 更新顯示狀態,包含關閉/點亮螢幕  
  32.     updateDisplayPowerStateLocked(dirtyPhase2);  
  33.   
  34.     // Phase 3: Send notifications, if needed.  
  35.     // 休眠/喚醒是否準備完成  
  36.     if (mDisplayReady) {  
  37.         sendPendingNotificationsLocked();  
  38.     }  
  39.   
  40.     // Phase 4: Update suspend blocker.  
  41.     // Because we might release the last suspend blocker here, we need to make sure  
  42.     // we finished everything else first!  
  43.     // 休眠/喚醒前最後一步,之後會跳轉到native層執行申請/釋放鎖的操作  
  44.     updateSuspendBlockerLocked();  
  45. }  
經過前文的分析,申請WakeLock之後會呼叫到updatePowerStateLocked()中,而在該方法中通過呼叫updateDisplayPowerStateLocked()更新顯示狀態,關鍵方法如下: [java]  view plain  copy   在CODE上檢視程式碼片 派生到我的程式碼片
  1. private void updateDisplayPowerStateLocked(int dirty) {  
  2.     //... ...省略  
  3.     mDisplayReady = mDisplayPowerController.requestPowerState(mDisplayPowerRequest,  
  4.                 mRequestWaitForNegativeProximity);  
  5.     //... ...省略  
  6. }  
mDisplayPowerController是frameworks/base/services/java/com/android/server/power/DisplayPowerController.java的物件,在PowerManagerService的SystemReady()方法中完成初始化。繼續檢視DisplayPowerController的requestPowerState方法: [java]  view plain  copy   在CODE上檢視程式碼片 派生到我的程式碼片
  1. public