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

本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉。

前置文章:

Android 4.4 Kitkat Phone工作流程浅析(一)__概要和学习计划

Android 4.4 Kitkat Phone工作流程浅析(二)__UI结构分析

Android 4.4 Kitkat Phone工作流程浅析(三)__MO(去电)流程分析

Android 4.4 Kitkat Phone工作流程浅析(四)__RILJ工作流程简析

Android 4.4 Kitkat Phone工作流程浅析(五)__MT(来电)流程分析

Android 4.4 Kitkat Phone工作流程浅析(六)__InCallActivity显示更新流程

Android 4.4 Kitkat Phone工作流程浅析(七)__来电(MT)响铃流程

Android 4.4 Kitkat Phone工作流程浅析(八)__Phone状态分析

Android 4.4 Kitkat Phone工作流程浅析(九)__状态通知流程分析

Android 4.4 Kitkat Phone工作流程浅析(十)__"通话显示"查询流程

概要

       在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<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);
    }

    // 完成善后工作,初始化以及挂断电话后会被调用
    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通话过程中控制屏幕亮灭

文中所涉及的图片资源,免积分下载

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。