Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 4.4 Kitkat Phone工作流程淺析(十一)__PSensor工作流程淺析

Android 4.4 Kitkat Phone工作流程淺析(十一)__PSensor工作流程淺析

編輯:關於Android編程

 

概要

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

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


本文來自http://blog.csdn.net/yihongyuelan 轉載請務必注明出處

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序列中,當通話狀態變更時回調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);
    }

    // 完成善後工作,初始化以及掛斷電話後會被調用
    public void 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
    public void orientationChanged(int orientation) {
        mOrientation = orientation;
        updateProximitySensorMode();
    }

    // 當通話狀態有改變則會通過InCallPresenter回調
    @Override
    public void 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
    public void onAudioMode(int mode) {
        updateProximitySensorMode();
    }

    // 撥號盤顯示時回調,此時禁用PSensor以便用戶輸入
    public void onDialpadVisible(boolean visible) {
        mDialpadVisible = visible;
        updateProximitySensorMode();
    }

    // Config改變時回調,如開啟物理鍵盤(如今許多設備都已不再配備物理鍵盤)
    public void onConfigurationChanged(Configuration newConfig) {
        mIsHardKeyboardOpen = newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
        // Update the Proximity sensor based on keyboard state
        updateProximitySensorMode();
    }

    // InCallUI顯示或隱藏時回調
    public void onInCallShowing(boolean showing) {
        if (showing) {
            mUiShowing = true;
        } else if (mPowerManager.isScreenOn()) {
            mUiShowing = false;
        }
        updateProximitySensorMode();
    }
通過查看不難發現,以上方法(除了構造方法以外)均調用了updateProximitySensorMode方法,整個通話過程中正是由其觸發PSensor的控制,關鍵代碼如下:
/**
 * 根據當前設備狀態,使用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) 設備開啟物理鍵盤(如今許多設備都已不再配備物理鍵盤)
 */
private void updateProximitySensorMode() {
    if (proximitySensorModeEnabled()) {
        synchronized (mProximityWakeLock) {
            final int 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
            final boolean 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);
                }
            }
        }
    }
}
通過以上代碼的分析可以知道,屏幕的亮滅有多種情況。在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

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;
}
②. 通過WakeLock的acquire和release方法Enable/Unenable PSensor
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方法中完成,關鍵代碼如下:

 

private final PowerManager mPowerManager;
private final 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的處理流程,關鍵代碼如下:

public void acquire() {
    //... ...省略
    acquireLocked();
}
Private void acquireLocked() {
    // ... ..省略 mService即PowerManagerService對象
    mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource);
}
對於上層應用來說PowerManager實際上只是一個接口,真正實現在frameworks/base/services/java/com/android/server/power/PowerManagerService.java中:
@Override
public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
        WorkSource ws) {
    //... ...省略
    try {
        acquireWakeLockInternal(lock, flags, tag, packageName, ws, uid, pid);
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
}

private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName,
        WorkSource ws, int uid, int pid) {
    synchronized (mLock) {
        //... ...省略
        updatePowerStateLocked();
    }
}
因為在本節中我們主要關心關閉/點亮屏幕的操作,期間省略了很多狀態更新以及權限檢查的代碼。在acquireWakeLockInternal最後調用了updatePowerStateLocked,該方法是PowerManagerService中最為重要的方法,也即Android的休眠/喚醒入口方法。其中具有具有5大關鍵步驟,核心代碼如下:
private void 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.
    final long 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();
}
經過前文的分析,申請WakeLock之後會調用到updatePowerStateLocked()中,而在該方法中通過調用updateDisplayPowerStateLocked()更新顯示狀態,關鍵方法如下:
private void updateDisplayPowerStateLocked(int dirty) {
    //... ...省略
    mDisplayReady = mDisplayPowerController.requestPowerState(mDisplayPowerRequest,
                mRequestWaitForNegativeProximity);
    //... ...省略
}
mDisplayPowerController是frameworks/base/services/java/com/android/server/power/DisplayPowerController.java的對象,在PowerManagerService的SystemReady()方法中完成初始化。繼續查看DisplayPowerController的requestPowerState方法:
public boolean requestPowerState(DisplayPowerRequest request,
        boolean waitForNegativeProximity) {
    //... ...省略
    if (changed && !mPendingRequestChangedLocked) {
        mPendingRequestChangedLocked = true;
        sendUpdatePowerStateLocked();
    }
    //... ...省略
}
這裡會執行sendUpatePowerStateLocked方法,該方法中會使用sendMessage()發送一個MSG_UPDATE_POWER_STATE消息,而該消息的接收者正是DisplayPowerController的內部類DisplayControllerHandler,並執行updatePowerState()方法。當第一次執行updatePowerState()方法時,僅register PSensor的Listener而不會繼續往後執行,關鍵代碼如下:
private void updatePowerState() {
    //... ...省略
	setProximitySensorEnabled(true);
    //... ....省略
}

private void 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:

private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
    @Override
    public void onSensorChanged(SensorEvent event) {
        if (mProximitySensorEnabled) {
            final long time = SystemClock.uptimeMillis();
            //從Event中獲取distance值,這個值與driver上報有關,不同設備有可能不同
            //從當前測試設備上返回的數據來看,靠近時distance=0.0,遠離時distance=1.0
            final float 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);
        }
    }
    //... ...省略
};

以上信息也可以在log中獲取到,如:

01-02 08:51:56.963 D/PowerManagerDisplayController(  735): P-Sensor Changed: false ->遠離
01-02 08:51:59.621 D/PowerManagerDisplayController(  735): P-Sensor Changed: true  ->靠近
01-02 08:52:22.510 D/PowerManagerDisplayController(  735): P-Sensor Changed: false ->遠離

 

在onSensorChanged()方法中會繼續調用handleProximitySensorEvent()進而調用debounceProximitySensor()方法,最後會調用到updatePowerState()方法。此時,該方法會根據PSensor的狀態決發起真正的關閉/點亮屏幕流程,關鍵代碼如下:

private void updatePowerState() {
    //... ...省略
    // Apply the proximity sensor.
        //acquire即Enable PSensor
        if (mPowerRequest.useProximitySensor) {
            // 如果當前不是滅屏狀態就執行
            if (mPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) {
                // 注冊PSensor監聽,即Enable PSensor
                setProximitySensorEnabled(true);
                // 如果不需要強制喚醒則執行
                if (!mPowerRequest.forceWakeUpEnable){
                    // 默認mScreenOffBecauseOfProximity = false
                    // 同時當前PSensor處於被遮擋當狀態,即物體靠近
                    if (!mScreenOffBecauseOfProximity
                            && mProximity == PROXIMITY_POSITIVE) {
                        mScreenOffBecauseOfProximity = true;
                        //回調PowerManagerService中的updatePowerStateLocked()
                        sendOnProximityPositiveWithWakelock();
                        if (DEBUG) {
                            Slog.d(TAG, setScreenOn false becaue of P-sensor);
                        }
                        // 根據PSensor狀態關閉屏幕,這塊原生AOSP是放在後面做的,MTK將其提前了
                        setScreenOn(false);
                    }
                }
            } else if (mScreenOffBecauseOfProximity
                        && mProximity == PROXIMITY_POSITIVE) {
                // 通話過程中設備PSensor被遮擋,屏幕關閉
                // 此時,對方掛斷電話後屏幕會保持關閉,直到PSensor不再被遮擋才會亮屏
                if (DEBUG) {
                    Slog.d(TAG, enable P-sensor and wait for P-sensor negative);
                }
                setProximitySensorEnabled(true);
            } else if (mPowerRequest.forceProximitySensorEnable) {
                if (DEBUG) {
                    Slog.d(TAG, force enable P-sensor for talking screen off timeout);
                }
                setProximitySensorEnabled(true);
                if (!mScreenOffBecauseOfProximity
                        && mProximity == PROXIMITY_POSITIVE) {
                    mScreenOffBecauseOfProximity = true;
                }
            } else {
            }
        } else {
            //realse WakeLock後執行,即Unenable PSensor
            if (mWaitingForNegativeProximity
                && mScreenOffBecauseOfProximity
                && mProximity == PROXIMITY_POSITIVE) {
                // 通話過程中設備PSensor被遮擋,屏幕關閉
                // 此時,對方掛斷電話後屏幕會保持關閉,直到PSensor不再被遮擋才會亮屏
                if (DEBUG) {
                    Slog.d(TAG, proximity wakelock released but still enable P-sensor and wait for P-sensor negative);
                }
                setProximitySensorEnabled(true);
            } else {
                setProximitySensorEnabled(false);
                mWaitingForNegativeProximity = false;
            }
        }
        //... ...省略
    } else {
        mWaitingForNegativeProximity = false;
    }
    //... ...省略
    // Animate the screen on or off.
    if (!mScreenOffBecauseOfProximity || mPowerRequest.forceWakeUpEnable) {
        if (wantScreenOn(mPowerRequest.screenState)) {
            // Want screen on
            if (!mElectronBeamOffAnimator.isStarted()) {
                // PSensor遮擋物遠離,點亮屏幕
                setScreenOn(true);
                //... ...省略
            }
        } else {
            // Want screen off.
            if (!mElectronBeamOnAnimator.isStarted()) {
                if (!mElectronBeamOffAnimator.isStarted()) {
                    if (mPowerState.getElectronBeamLevel() == 0.0f || mShutDownFlag_D) {
                        // 動畫執行完畢後關閉屏幕
                        setScreenOn(false);
                        //... ...省略
                }
            }
        }
    }
    //... ...省略
}

updatePowerState主要功能如下:

1. 啟用或禁用PSensor;這部分之前有做分析,不再贅述。在MTK代碼中,PSensor的滅屏操作也放在了這塊代碼中處理,而原生AOSP滅屏操作放在動畫執行之前。

2. 設置亮度具體值;該亮度值主要用於設置亮滅屏動畫。
animateScreenBrightness(clampScreenBrightness(target),slow ? BRIGHTNESS_RAMP_RATE_SLOW : BRIGHTNESS_RAMP_RATE_FAST);

3. 執行滅屏/亮屏動畫;執行動畫前會調用setScreenOn()方法關閉/點亮屏幕。原生AOSP將PSensor的亮滅屏處理也放在這塊代碼中,AOSP中代碼結構如下

if (mScreenOffBecauseOfProximity) {
    setScreenOn(false);
} else if(wantScreenOn(mPowerRequest.screenState)) {
    // Want screen on
    // 亮屏動畫
    mElectronBeamOnAnimator.start();
    //... ...省略
    mElectronBeamOnAnimator.end();
} else {
    // Want screen off.
    // 滅屏動畫
    mElectronBeamOffAnimator.start();
    //... ...省略
    mElectronBeamOffAnimator.end();
}

MTK則是將 if (mScreenOffBecauseOfProximity)提到了PSensor啟用/禁用代碼中,修改之後的代碼邏輯更為清晰。

通過setScreenOn()會最終執行到LightService的setLightLocked()方法中,並通過JNI的方式將亮度值傳遞給背光模組。

setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);

PSensor關閉/點亮屏幕小結

屏幕的關閉與點亮最終是由LightService來做的,而PSensor僅僅充當了觸發條件。在設備屏幕關閉/點亮流程中,需要注意以下關鍵點:

 

1. 第一次執行WakeLock的acquire()方法時並不會關閉屏幕,而是注冊PSensor的EventListener;

2. PROXIMITY_SCREEN_OFF_WAKE_LOCK鎖acquire/release表示register/unregister PSensor EventListener;

即決定是否將PSensor作為屏幕關閉/點亮的控制條件,acquire()表示啟用該控制條件,release()表示取消該控制條件。

3. PSensor EventListener回調onSensorChanged()觸發屏幕背光控制;

onSensorChanged()最終會調用updatePowerState()方法,根據PSensor是否被遮擋從而執行屏幕背光的設置,並在背光設置完成後回調到PowerManagerService的updatePowerStateLocked方法中繼續後面的流程。

 

4. 屏幕關閉/點亮會觸發Android休眠/喚醒機制;

在PowerManagerService中,updatePowerStateLocked是系統休眠/喚醒的上層入口,當更新完設備狀態後通過JNI的方式,觸發系統進入休眠/喚醒流程。

PSensor控制屏幕背光流程如圖3:

\

圖 3 PSensor控制屏幕背光流程

系統休眠/喚醒

前文分析了PSensor是如何控制屏幕關閉/點亮,即通過PowerManagerService中的updateDisplayPowerStateLocked()並最終調用LightService的setLight_native()方法完成。當完成屏幕關閉/點亮之後會繼續執行updatePowerStateLocked()中余下流程,其中最重要的就是updateSuspendBlockerLocked()。該方法觸發系統了休眠/喚醒機制,關鍵代碼如下:

private void updateSuspendBlockerLocked() {
    //... ...省略
    //acquire WakeLock,系統中如有WakeLock沒有釋放則無法進入深度休眠
    if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) {
        mWakeLockSuspendBlocker.acquire();
        mHoldingWakeLockSuspendBlocker = true;
    }
    //... ...省略

    // release WakeLock,釋放系統的WakeLock以使系統可以正常進入休眠
    if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) {
        mWakeLockSuspendBlocker.release();
        mHoldingWakeLockSuspendBlocker = false;
    }
    //... ..省略
}
這裡的acquire和release最終調用到frameworks/base/services/jni/com_android_server_power_PowerManagerService.cpp中:
static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass clazz, jstring nameStr) {
    ScopedUtfChars name(env, nameStr);
    acquire_wake_lock(PARTIAL_WAKE_LOCK, name.c_str());
}

static void nativeReleaseSuspendBlocker(JNIEnv *env, jclass clazz, jstring nameStr) {
    ScopedUtfChars name(env, nameStr);
    release_wake_lock(name.c_str());
}

從代碼中可以看到,系統實際處理的WakeLock只有PARTIAL_WAKE_LOCK,再往後就是Android的休眠/喚醒流程了,這裡不再詳述。

需要注意的是Android JellyBean之前的系統休眠/喚醒流程大致如:earlysuspend -> wake_unlock -> suspend -> late suspend -> sleep -> wakeup -> early resume -> resume -> late resume,而在Android JellyBean之後Android使用AutoSleep逐漸替代了earlysuspend。earlysuspend和WakeLock是Android創造的一種高效休眠機制,無奈這種修改得不到Linux社區的承認,最終只能在Linux的Wakeup機制上重新修改。雖然Android在JellyBean中已經移除了earlysuspend機制,但MTK依然在AutoSleep的基礎上加入了經過修改後的earlysuspend,而Qcom默認使用的是AutoSleep,不過也有個別ODM廠商將earlysuspend機制加入到Qcom代碼中。

系統休眠/喚醒小結

在PSensor觸發滅屏操作之後,系統實際上會進入休眠流程,關於休眠/喚醒的具體流程可查看參考6 參考7。通過前面的分析可以知道,因為申請了PARTIAL_WAKE_LOCK,因此在try_to_suspend()方法中會一直循環檢測,不會執行pm_suspend()。也就是說此時系統並沒有完全休眠( MTK平台會完成earlysuspend ),休眠流程開始部分時序如圖4:

\

圖 4 休眠流程(開始部分)

總結

本文主要分析了在InCallUI中PSensor如何控制屏幕的亮滅及其實現原理。全文分為三大部分,即:ProximitySensor初始化流程,ProximitySensor使用流程和PSensor工作流程。前兩部分主要分析了上層應用如何使用PSensor提供的接口,第三部分則詳細分析了PSensor如何控制屏幕的關閉/點亮。

ProximitySensor初始化流程需要注意:

1. ProximitySensor初始化是在InCallPresenter中完成;

2. ProximitySensor的初始化依賴於InCallUI中CallHandlerService的綁定,Call 狀態改變觸發綁定;

ProximitySensor使用流程需要注意:

1.在InCallUI中通過ProximitySensor提供的接口,調用updateProximitySensorMode()更新PSensor的Enable狀態;

2. 在ProximitySensor中,通過acquire/release PowerManager.WakeLock來 Enable/Unenalbe PSensor;

PSensor工作流程需要注意:

因為PSensor工作流程細分為WakeLock申請、PSensor關閉/點亮屏幕、系統休眠/喚醒三個部分,各個部分都有一些需要注意的地方。

WakeLock:

1. PROXIMITY_SCREEN_OFF_WAKE_LOCK普通APP無法申請,在系統中即PSensor專用 ( 實際在底層對應的還是PARTIAL_WAKE_LOCK );

2. 使用WakeLock需要在AndroidManifest.xml添加android.permission.WAKE_LOCK權限;

PSensor點亮/關閉屏幕需要注意:

PSensor關閉/點亮屏幕流程分為兩步:注冊PSensor Listener和PSensor狀態觸發亮滅屏,其中需要注意:

1. 第一次執行WakeLock的acquire()方法時並不會關閉屏幕,僅僅是注冊PSensor的EventListener;

2. PROXIMITY_SCREEN_OFF_WAKE_LOCK鎖acquire/release表示register/unregister PSensor EventListener;

3. 根據PSensor EventListener回調onSensorChanged()觸發屏幕背光控制;

4. 屏幕亮滅會觸發Android休眠/喚醒機制;

系統休眠/喚醒需要注意:

1. PROXIMITY_SCREEN_OFF_WAKE_LOCK實際上對應底層的PARTIAL_WAKE_LOCK,該鎖會保持CPU持續運轉不會進入休眠;

2. Android JellyBean之後默認不再使用earlysuspend,取而代之的是AutoSleep機制,對上層來說沒有影響;

3. 如果系統支持earlysuspend,則PSensor滅屏會使系統走完earlysuspend流程,最終在try_to_sleep()方法中一直循環檢測;

綜上,PSensor在整個通話過程中實現屏幕亮滅的控制流程如圖5所示:

\

圖 5 PSensor通話過程中控制屏幕亮滅

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved