概要
在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工作流程浅析(九)__状态通知流程分析》中已经分析通话状态变更流程 ( 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();
- }
- /**
- * 根据当前设备状态,使用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");
- }
- }
- }
- }
- }
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方法中完成,关键代码如下:
- 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);
- }
- @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();
- }
- }
- 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();
- }
- private void updateDisplayPowerStateLocked(int dirty) {
- //... ...省略
- mDisplayReady = mDisplayPowerController.requestPowerState(mDisplayPowerRequest,
- mRequestWaitForNegativeProximity);
- //... ...省略
- }
- public boolean requestPowerState(DisplayPowerRequest request,
- boolean waitForNegativeProximity) {
- //... ...省略
- if (changed && !mPendingRequestChangedLocked) {
- mPendingRequestChangedLocked = true;
- sendUpdatePowerStateLocked();
- }
- //... ...省略
- }
- 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);
- }
- }