这是一篇补档博客,整理记录的时候发现这篇没有发上来。
首次编辑完成时间是2016.05.02。过时了过时了。
Android L无法接听/拒接来电现象分析与解决(文末对比Android M)
关键代码
接听:
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的显示。
有人可能会问,为什么在call disconnect以后call没有被清理掉?
下面就是原因:
@Override
public void onDisconnect(Call call) {
// no-op
}
因为onDisconnect()里面什么都没有做、、
在处理这个问题的过程中,我发现只要我们停留在短信拒接选择短信的界面,对方挂断来电,那么之后本机再选择短信都是发不出去的。
之前的原因是getActivePhoneId()返回了的phoneId对应IDLE的call,加入修改后getActivePhoneId()返回-1,不对应任何call。无论哪种原因,都是发不出去短信的。那么我们保存最后Incoming的call,另外开辟一条发送短信的路就是了,具体方法不写出来了。
在M上有人发现了不能接听/拒接来电的问题(好像M上复现起来更容易,步骤更简单),于是他在onDisconnect()方法里将mCall = null了(http://review.cyanogenmod.org/#/c/124087/)。
这样修改可以解决这个问题,但是我上面提到的来电断开后不能成功发送短信的问题还是存在的(Mokee开发群里就有人发现了这个问题,本以为这个问题几乎不会有人发现,后来我还是改了一下提交到Mokee了,解法与L类似。)
call的状态的变化有时候并不如我们预料的那样,CallList里面不存在的call不一定在其他地方不存在,有可能因为其他原因call的生命被延长了。