与Dialer相关的ProximitySensor传感器

一开始想,既然视频通话永远不灭,语音通话关闭免提会灭屏。那么就想,应该是会灭屏情况,调用SensorManager的registerListener;永不灭屏时,根本调用unRegisterListener。Sensor的类型是TYPE_PROXIMITY。 但是这么搜索没有收获。

Dialer中ProximitySensor.java等文件也和距离传感器有关。但是逻辑较多没有找到入口。

后来才知道,原来我搜索的方法太底层了。拨号盘在某些地方的距离传感器灭屏功能是用SensorManager registerListener实现。

而此问题的功能拨号盘是用PowerManager的WakeLOCK 功能实现。

怎么定位的呢,用老方法,抓对比log,看看log有什么差异吧。

拨号盘通话过程中靠近灭屏的代码入口

果真抓音频和视频通话 关闭打开外放,手靠近和远离时的log。通过audio,earpiece等关键字查询,有如下明显差异。

一个打开传感器,一个关闭传感器。从上面变量查看,screenOnImmediately变量有差异。然后查看代码,问题果真在这里。

#语音通话关闭外放
08-24 09:42:46.135  2688  2688 I Dialer  : CallButtonPresenter.setAudioRoute - sending new audio route: EARPIECE, WIRED_HEADSET
08-24 09:42:46.143  2688  2688 I Dialer  : ProximitySensor.updateProximitySensorMode - screenOnImmediately: false, dialPadVisible: false, offHook: true, horizontal: true, uiShowing: true, audioRoute: EARPIECE
08-24 09:42:46.143  2688  2688 V Dialer  : ProximitySensor.updateProximitySensorMode - turning on proximity sensor
08-24 09:42:46.143  2688  2688 I Dialer  : ProximitySensor.turnOnProximitySensor - acquiring wake lock

#视频通话关闭外放
08-24 10:02:33.096  2698  2698 I Dialer  : SpeakerButtonController.setSupportedAudio - audioState: [AudioState isMuted: false, route: EARPIECE, supportedRouteMask: EARPIECE, SPEAKER]
08-24 10:02:33.095  2698  2698 I Dialer  : ProximitySensor.updateProximitySensorMode - screenOnImmediately: true, dialPadVisible: false, offHook: true, horizontal: true, uiShowing: true, audioRoute: EARPIECE
08-24 10:02:33.095  2698  2698 V Dialer  : ProximitySensor.updateProximitySensorMode - turning off proximity sensor
08-24 10:02:33.096  2698  2698 I Dialer  : ProximitySensor.turnOffProximitySensor - wake lock already released

代码如下,在updateProximitySensorMode函数中,判断是否开启基于距离传感器的亮灭屏功能,设置screenOnImmediately。

如果是通话中,并且screenOnImmediately 为false,则开启距离传感器的亮灭屏功能。否则关闭距离传感器功能,则靠近屏幕不会息屏。

视频通话时,mIsVideoCall 是true。则screenOnImmediately是TRUE,所以关闭距离传感器功能。

通话靠近远离亮灭屏功能开启和关闭分别由turnOnProxmitySensor()和turnOnProxmitySensor()

PowerManager的WakeLOCK

接下来看看turnOnProximitySensor() 和turnOffProximitySensor()函数。

顾名思义,是打开和关闭距离传感器。如何做到呢?就是通过PowerManger提供的WakeLock接口实现的。

当然其实也可以直接通过SensorManager(TYPE_PROXIMITY)的registerListener()来开启远近事件的监听,在监听回调中根据远近触发亮灭屏。

不过PowerManager提供的WakeLock封装了不同级别的亮灭屏方案,其中就包括距离传感器触发亮灭屏。

WakeLock的类型需要选择为PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK。打开就是WakeLock.acquire,关闭就是WakeLock.release。 代码如下:

 WakeLOCK类别

WakeLock 分类如下:

  • PARTIAL_WAKE_LOCK: 灭屏,关闭键盘背光的情况下,CPU依然保持运行。
  • PROXIMITY_SCREEN_OFF_WAKE_LOCK: 基于距离感应器熄灭屏幕。最典型的运用场景是我们贴近耳朵打电话时,屏幕会自动熄灭。
  • SCREEN_DIM_WAKE_LOCK/SCREEN_BRIGHT_WAKE_LOCK/FULL_WAKE_LOCK:这三种WakeLock都已经过时了,它们的目的是为了保持屏幕长亮,Android官方建议用getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);方式替换。因为比起申请WakeLock,这种方式更简单,还不需要特别申请android.permission.WAKE_LOCK权限。
  • DOZE_WAKE_LOCK/DRAW_WAKE_LOCK: 隐藏的分类,系统级别才会用到。DreamManagerService 会申请DOZE_WAKE_LOCK。WindowsManagerService 中的WindowsState.java会申请DRAW_WAKE_LOCK。

建议供用户使用的是这两个:PARTIAL_WAKE_LOCK和PROXIMITY_SCREEN_OFF_WAKE_LOCK。

 

PROXIMITY_SCREEN_OFF_WAKE_LOCK

对应用来说,通过对mProximityWakeLock.acquire即可以实现近距离灭屏,远距离亮屏了。后面单独写一篇PROXIMITY_SCREEN_OFF_WAKE_LOCK 流程的文章。针对原生拨号盘 视频通话时关闭免提 靠近屏幕 屏幕不灭这个问题,分析到这里就够了。

from

PseudoProximityWakeLock唤醒锁

在AnswerScreenPresenter中的onCreate方法中,根据条件判断决定是否将距离传感器初始化好,AnswerScreenPresenter就相当于之前版本的AnswerPresenter。

在来电时AnswerScreenPresenter初始化时,会判断Answerproximiysensor.shouldUse函数,如果返回true,则会生成Answerproximiysensor。

Answerproximiysensor 构造函数,通过answerProximityWakeLock.acquire注册距离传感器。

Answerproximiysensor.shouldUse函数在灭屏时才会返回true。

    PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState();
    if (AnswerProximitySensor.shouldUse(context, call)) {
      new AnswerProximitySensor(context, call, pseudoScreenState);
    } else {
      pseudoScreenState.setOn(true);
    }

我们现在来看看详细的判断:
1.不是来电状态
2.不允许使用来电传感器
3.不支持距离传感器
4.当前状态时亮屏

public static boolean shouldUse(Context context, DialerCall call) {
    // Don't use the AnswerProximitySensor for call waiting and other states. Those states are
    // handled by the general ProximitySensor code.
    if (call.getState() != State.INCOMING) {               
      LogUtil.i("AnswerProximitySensor.shouldUse", "call state is not incoming");
      return false;
    }

    if (!ConfigProviderBindings.get(context)
        .getBoolean(CONFIG_ANSWER_PROXIMITY_SENSOR_ENABLED, true)) {
      LogUtil.i("AnswerProximitySensor.shouldUse", "disabled by config");
      return false;
    }

    if (!context
        .getSystemService(PowerManager.class)
        .isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
      LogUtil.i("AnswerProximitySensor.shouldUse", "wake lock level not supported");
      return false;
    }

    if (isDefaultDisplayOn(context)) {
      LogUtil.i("AnswerProximitySensor.shouldUse", "display is already on");
      return false;
    }

    return true;
  }

PseudoScreenState类的代码如下:

但手第一次靠近时,距离传感器通知answerProximityWakeLock.onSensorChanged,进而通知pseudoScreenState.setOn();

pseudoScreenState.setON 通知其内部的listener.onPseudoScreenStateChanged();

其listener是什么?是IncallActivity..->其onResume函数调用了pseudoScreenState.addListener(this).

所以IncallActivity.onPseudoScreenStateChanged被调用,isON为false,导致pseudoBlackScreenOverlay黑屏界面visible,所以界面除了状态栏显示以外,其他黑屏。

 public class PseudoScreenState {

  /** Notifies when the on state has changed. */
  public interface StateChangedListener {
    void onPseudoScreenStateChanged(boolean isOn);
  }

  private final Set<StateChangedListener> listeners = new ArraySet<>();

  private boolean on = true;

  public boolean isOn() {
    return on;
  }

  public void setOn(boolean value) {
    if (on != value) {
      on = value;
      for (StateChangedListener listener : listeners) {
        listener.onPseudoScreenStateChanged(on);
      }
    }
  }

  public void addListener(StateChangedListener listener) {
    listeners.add(listener);
  }

  public void removeListener(StateChangedListener listener) {
    listeners.remove(listener);
  }
}

AnswerProximitySensor的构造函数如下:

public AnswerProximitySensor(
      Context context, Call call, PseudoScreenState pseudoScreenState) {
    this.call = call;
    Log.d("AnswerProximitySensor.constructor", "acquiring lock");
    if (ConfigProviderBindings.get(context).getBoolean(CONFIG_ANSWER_PSEUDO_PROXIMITY_WAKE_LOCK_ENABLED, true)) {
      answerProximityWakeLock = new PseudoProximityWakeLock(context, pseudoScreenState);
    } else {
      // TODO: choose a wake lock implementation base on framework/device.
      // These bugs requires the PseudoProximityWakeLock workaround:
      // b/30439151 Proximity sensor not working on M
      // b/31499931 fautly touch input when screen is off on marlin/sailfish
      answerProximityWakeLock = new SystemProximityWakeLock(context);
    }
    answerProximityWakeLock.setScreenOnListener(this);
    answerProximityWakeLock.acquire();

    CallList.getInstance().addListener(this);
  }

在电话状态发生改变和远离距离传感器的情况下,将
在InCallActiivityde onResume方法中进行了调用:

PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState();
pseudoScreenState.addListener(this);
onPseudoScreenStateChanged(pseudoScreenState.isOn());

在onPause方法中将监听移除:

InCallPresenter.getInstance().getPseudoScreenState().removeListener(this);

在InCallActivity中,pseudoBlackScreenOverlay是一个黑色的浮层,当满足来电防误触的情况下时,将浮层设置为VISIBLE,使得触摸事件不响应,同时在dispatchTouchEvent方法中,直接返回true,不继续分发触摸事件

    private View pseudoBlackScreenOverlay;
    private boolean touchDownWhenPseudoScreenOff;

    @Override
    public void onPseudoScreenStateChanged(boolean isOn) {
        Log.d("InCallActivity.onPseudoScreenStateChanged", "isOn: " + isOn);
        pseudoBlackScreenOverlay.setVisibility(isOn ? View.GONE : View.VISIBLE);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        // Reject any gesture that started when the screen is in the fake off state.
        if (touchDownWhenPseudoScreenOff) {
            if (event.getAction() == MotionEvent.ACTION_UP) {
                touchDownWhenPseudoScreenOff = false;
            }
            return true;
        }
        // Reject all touch event when the screen is in the fake off state.
        if (!InCallPresenter.getInstance().getPseudoScreenState().isOn()) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                touchDownWhenPseudoScreenOff = true;
                Log.d("InCallActivity.dispatchTouchEvent", "touchDownWhenPseudoScreenOff");
            }
            return true;
        }
        return super.dispatchTouchEvent(event);
    }

 

链接:https://www.jianshu.com/p/f9ff07f56fe3
 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值