Android L无法接听/拒接来电现象的分析与解决(文末对比Android M)

问题描述

  1. 卡2收到来电,上滑选择短信拒接,仅停留在界面不选择短信,
  2. 此时卡2上的来电被对方取消,卡1收到来电
  3. 此时无法接听或拒接卡1上的来电

分析问题

  1. 假设对于卡1上的来电可以成功下发answer和decline的命令,那么界面上应该会有正常的响应,所以这个问题很可能出现在InCallUI内;
  2. 找到answer和decline的代码,查看是否有可疑之处,必要时打断点debug。

关键代码 接听:

 	public void onAnswer(int videoState, Context context) {
        int phoneId = getActivePhoneId();
        Log.i(this, "onAnswer  mCallId:" + mCallId + "phoneId:" + phoneId + " videoState="
                + videoState);
        if (mCallId == null || phoneId == -1) {
            return;
        }
		...
    }

拒接:

	/**
     * TODO: We are using reject and decline interchangeably. We should settle on
     * reject since it seems to be more prevalent.
     */
    public void onDecline(Context context) {
        int phoneId = getActivePhoneId();
        Log.i(this, "onDecline mCallId:" + mCallId + "phoneId:" + phoneId);
        if (mCall[phoneId].getSessionModificationState()
                == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
            InCallPresenter.getInstance().declineUpgradeRequest(context);
        } else {
            TelecomAdapter.getInstance().rejectCall(mCall[phoneId].getId(), false, null);
        }
    }

看到这两个方法里都有log打印出来,那么我分别打印正常和异常的log做比较看能都发现异常。

//按照上面步骤操作得到的异常log:
05-02 14:04:11.318 7643-7643/com.android.incallui I/InCall: AnswerPresenter - onDecline mCallId:[Ljava.lang.String;@1734d9f8phoneId:1
05-02 14:04:13.603 7643-7643/com.android.incallui I/InCall: AnswerPresenter - onAnswer  mCallId:[Ljava.lang.String;@1734d9f8phoneId:1 videoState=0
//只有卡1有一个来电的拒接操作:
05-02 14:05:47.048 7643-7643/com.android.incallui I/InCall: AnswerPresenter - onDecline mCallId:[Ljava.lang.String;@15b5b6e1phoneId:0

注意到虽然两次操作都是针对卡1上的来电,但是输出的phoneId却不一样,可以理解为出现异常的时候,我们打算对卡1的来电进行操作,实际上是对卡2上的电话的操作,但是实际上卡2的来电不是已经断开了么?而且卡2上的call断开以后其状态很可能已经变为IDLE,状态的为IDLE的call如何被接听?为什么会出现这种情况呢?我们继续分析。

从前面的代码段中可以看到phoneId是getActivePhoneId()的返回值,那么可以的地方就是这个方法了。

	// get active phoneId, for which call is visible to user
    private int getActivePhoneId() {
        int phoneId = -1;
        if (CallList.getInstance().isDsdaEnabled()) {
            int subId = CallList.getInstance().getActiveSubscription();
            phoneId = CallList.getInstance().getPhoneId(subId);
        } else {
            for (int i = 0; i < mCall.length; i++) {
                if (mCall[i] != null) {
                    phoneId = i;//phoneId的值
                }
            }
        }
        return phoneId;
    }

乍一看这个方法似乎没有问题,但真的没问题么? 按照常理,这个方法写在AnswerPresenter.java里,那么可供操作的对象一定是INCOMING或者CALL_WAITING的call,因为来电界面一旦消失,那么这两种状态的call的状态也会立即跟着转变(我们假设当初写这个方法的作者也是这样想的),但是实际情况是,当停留在短信拒接的选择界面时,AnswerFragment的生命被延长了,即使call已经断开,界面没有消失,那么对应的call也就存留了下来。也就导致getActivePhoneId()方法有可能返回IDLE的call。

问题解法

方法一

其实这个方法的意图是返回一个有可“操作”的call的SIM卡对应的phoneId。那如果我们认定IDLE是不可操作的,那么我们把这个状态过滤掉就好了。改写判断条件:

	if ((mCall[i] != null) && (mCall[i].getState() != Call.State.IDLE)) {
        phoneId = i;
    }

但是实质上,更深入的想一下,这里想要获得是一个INCOMING或者CALL_WAITING的call所对应的phoneId,可修改条件为:

	if ((mCall[i] != null) && ((mCall[i].getState() == Call.State.INCOMING)
                || (mCall[i].getState() == Call.State.CALL_WAITING))) {
		phoneId = i;
		break;
    }

这样的话每次这个方法每次返回的phoneId对应的都是有来电的那张卡(修改已提交给CM http://review.cyanogenmod.org/#/c/109759/)。

方法二:

换个思路,问题复现的一个必要步骤是,在第一次来电的时候上滑停留在短信拒接选择短信的界面,之后来电断开,在之后问题复现。 那么如果我们在来电断开后就立刻dismiss掉选择拒接短信的Dialog,这个问题也可以被解决(OPPO就是这么做的)。 这么修改似乎都可以说的过去,但是,短信拒接的界面还提供了另一个选项“自定义短信回复”,如果用户正在编辑一串短信,而这个Dialog突然就随着来电的断开就dismiss掉的话,有点不合常理不近人情的感觉。所以我个人建议还是保留Dialog的显示。

其他

Q&A

有人可能会问,为什么在call disconnect以后call没有被清理掉? 下面就是原因:

    @Override
    public void onDisconnect(Call call) {
        // no-op
    }

因为onDisconnect()里面什么都没有做、、

附加问题

在处理这个问题的过程中,我发现只要我们停留在短信拒接选择短信的界面,对方挂断来电,那么之后本机再选择短信都是发不出去的。 之前的原因是getActivePhoneId()返回了的phoneId对应IDLE的call,加入修改后getActivePhoneId()返回-1,不对应任何call。无论哪种原因,都是发不出去短信的。那么我们保存最后Incoming的call,另外开辟一条发送短信的路就是了,具体方法不写出来了。

对比Android M

在M上有人发现了不能接听/拒接来电的问题(好像M上复现起来更容易,步骤更简单),于是他在onDisconnect()方法里将mCall = null了(http://review.cyanogenmod.org/#/c/124087/)。 这样修改可以解决这个问题,但是我上面提到的来电断开后不能成功发送短信的问题还是存在的(Mokee开发群里就有人发现了这个问题,本以为这个问题几乎不会有人发现,后来我还是改了一下提交到Mokee了,解法与L类似。)

总结

call的状态的变化有时候并不如我们预料的那样,CallList里面不存在的call不一定在其他地方不存在,有可能因为其他原因call的生命被延长了。