转载请注明出处:https://blog.csdn.net/turtlejj/article/details/83898624,谢谢~
以前遇到过用户报市场问题,手机的通知中心一直显示用户当前Sim卡设置了无条件呼叫转移,但其实呼入的电话可以正常接到,进入电话设置页面查看,发现呼叫转移的SwitchButton也处于关闭状态。最后通过log发现,用户的Sim卡被设置了serviceClass为"SERVICE_CLASS_DATA_SYNC"的无条件呼叫转移。电话设置页面仅仅过滤出serviceClass为"SERVICE_CLASS_VOICE"的呼叫转移,因此SwitchButton处于关闭状态;但通知中心并没有对serviceClass进行过滤,所以显示用户设置了无条件呼叫转移。
对通知中心的代码添加过滤很容易,但用户的问题没办法通过通常的UI操作来解决,因为我们的手机在常规的操作下,serviceClass默认都是"SERVICE_CLASS_VOICE"。为此,我特地去研究了一下如何通过MMI Code来帮助用户取消这个不正常的呼叫转移状态。
用于解析补充业务(Supplementary Service) MMI Code的核心代码都在frameworks/opt/telephony仓库下的GsmMmiCode.java文件中。
首先,我们要知道手机是如何识别MMI码的。这里涉及了正则表达式的知识。
// See TS 22.030 6.5.2 "Structure of the MMI"
static Pattern sPatternSuppService = Pattern.compile(
"((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
/* 1 2 3 4 5 6 7 8 9 10 11 12
1 = Full string up to and including #
2 = action (activation/interrogation/registration/erasure)
3 = service code
5 = SIA
7 = SIB
9 = SIC
10 = dialing number
*/
没学过正则表达式的同学估计要看懵了,这是什么鬼东西,感觉像是胡乱按键盘打出来的。但其实,正则表达式是按照一定规范编写出来,用来匹配一定格式的字符串用的,下面我们就来看看它是怎么工作的。
关于Java中使用正则表达式的方法,我们这里不多说,不清楚的同学可以参考《Java 正则表达式》,里面详细介绍了正则表达式的匹配方法。
回到代码中,我们一步一步分解来看。
首先,使用"(\\*|#|\\*#|\\*\\*|##)",匹配以"*" "#" "*#" "**" 或 "##" 开头的字符串(其中,"\"为转义字符,"|"代表"或")。
第二步,使用"(\\d{2,3})",匹配一个2位或者3位的数字(其中,"\d"用来匹配数字,{2,3}表示最少匹配2次,至多匹配3次)。
第三步,使用(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#,匹配最多连续四组以*开头,其余部分为除了*或#以外的字符串。这一部分的结尾匹配一个"#"。
这里有一点儿不太容易看明白,但其实,可以观察到,这里其实是由好几个"\\*([^*#]*)"嵌套在一起组成的,拆分来说明一下:
"\\*",不用多说,用来匹配一个"*"字符。
([^*#]*),其中前半部分的"[^*#]"用来匹配除了*或#以外的任意单个字符;最后面的"*"表示,对前面的[^*#]匹配零次或多次。
由此,我们可知,当"[^*#]"匹配了3次时,"\\*([^*#]*)"匹配到的字符串诸如"*XYZ";而当且仅当"[^*#]"匹配零次时,"\\*([^*#]*)"匹配到的字符串为"*"。
而每一组后面的"?",则表示,前面的匹配可以出现零次或一次。
第四步,使用(.*),匹配一个除了"\r\n"外,任意长度的字符串(字符串长度可以为0)。
按照3GPP协议,正则表达式所匹配出来的每一部分,都有一个名字,我们来按照名字来写一下:
**SERVICE_CODE*SIA*SIB*SIC*PWD_CONFIRM#DIALING_NUMBER
其中,最前面的部分叫做ACTION,为了体现出格式,我们用"**"表示(也可以是"*" "#" "*#" 或 "##"中的任意一种)。
而上面我们第三步中所讲的,即这里的"*SIA*SIB*SIC*PWD_CONFIRM#",我们也说了,后面的"?"表示,他们可以出现零次或一次,所以,其实这里可以是如下的任意一种形式:
之所以这样设计,是因为,并不是所有的MMI Code中都会同时使用到SIA、SIB、SIC和PWD_CONFIRM这四个值,有可能只需要其中的一个或者两个。
关于正则表达式的匹配,我们就说到这里,这里确实比较乱,如果对正则表达式不是特别熟悉,可以先去看看最基本的正则表达式规则(注意: Java中的正则表达式与其他地方用的正则表达式略有不同,学习的时候不要混淆),而后在纸上或者Notepad中将正则表达式分成一小块一小块来进行解析,慢慢阅读其含义。
好,那么我们回到主题,如何使用MMI Code来设置/取消/查询呼叫转移呢?
首先我们查看代码中的一些定义:
// From TS 22.030 6.5.2
static final String ACTION_ACTIVATE = "*"; // 激活呼叫转移业务
static final String ACTION_DEACTIVATE = "#"; // 去激活呼叫转移业务
static final String ACTION_INTERROGATE = "*#"; // 查询呼叫转移
static final String ACTION_REGISTER = "**"; // 注册呼叫转移
static final String ACTION_ERASURE = "##"; // 取消呼叫转移
// Call Forwarding
static final String SC_CFU = "21"; // 无条件转移
static final String SC_CFB = "67"; // 遇忙转移
static final String SC_CFNRy = "61"; // 不可及转移
static final String SC_CFNR = "62"; // 无应答转移
static final String SC_CF_All = "002"; // 所有呼叫转移
static final String SC_CF_All_Conditional = "004"; // 所有条件呼叫转移(不包含无条件转移)
我们一般在使用电话设置的UI向运营商注册呼叫转移时,所使用的是ACTION_REGISTER,即"**";取消时所使用的是ACTION_ERASURE,即"##";而查询时,则是ACTION_INTERROGATE,即"*#"。
我们查看GsmMmiCode.java中processCode()方法对CF的处理:
1. 匹配到的ServiceCode:"**"代表注册呼叫转移,"##"代表取消呼叫转移,"*#"代表查询呼叫转移
2. 匹配到的SIA:要转移给哪个号码
3. 匹配到的SIB:对哪个ServiceClass设置呼叫转移
4. 匹配到的SIC:在何种情况下进行呼叫转移(即无条件转移、遇忙转移等)
/** Process a MMI code or short code...anything that isn't a dialing number */
public void
processCode() throws CallStateException {
try {
......
// 使用匹配到的Service Code,判断是否为上面所列出的呼叫转移相关的值
} else if (isServiceCodeCallForwarding(mSc)) {
Rlog.d(LOG_TAG, "processCode: is CF");
// 匹配到的SIA,是要转移给哪一个号码
String dialingNumber = mSia;
// 匹配到的SIB,是ServiceClass
int serviceClass = siToServiceClass(mSib);
// 匹配到的SIC,为何种情况下进行转移(即无条件转移、遇忙转移等)
int reason = scToCallForwardReason(mSc);
int time = siToTime(mSic);
if (isInterrogate()) {
mPhone.mCi.queryCallForwardStatus(
reason, serviceClass, dialingNumber,
obtainMessage(EVENT_QUERY_CF_COMPLETE, this));
} else {
int cfAction;
if (isActivate()) {
// 3GPP TS 22.030 6.5.2
// a call forwarding request with a single * would be
// interpreted as registration if containing a forwarded-to
// number, or an activation if not
if (isEmptyOrNull(dialingNumber)) {
cfAction = CommandsInterface.CF_ACTION_ENABLE;
mIsCallFwdReg = false;
} else {
cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
mIsCallFwdReg = true;
}
} else if (isDeactivate()) {
cfAction = CommandsInterface.CF_ACTION_DISABLE;
} else if (isRegister()) {
cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
} else if (isErasure()) {
cfAction = CommandsInterface.CF_ACTION_ERASURE;
} else {
throw new RuntimeException ("invalid action");
}
int isEnableDesired =
((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
(cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
Rlog.d(LOG_TAG, "processCode: is CF setCallForward");
mPhone.mCi.setCallForward(cfAction, reason, serviceClass,
dialingNumber, time, obtainMessage(
EVENT_SET_CFF_COMPLETE,
isVoiceUnconditionalForwarding(reason, serviceClass) ? 1 : 0,
isEnableDesired, this));
}
}
......
} catch (RuntimeException exc) {
mState = State.FAILED;
mMessage = mContext.getText(com.android.internal.R.string.mmiError);
Rlog.d(LOG_TAG, "processCode: RuntimeException=" + exc);
mPhone.onMMIDone(this);
}
}
我们在电话设置的UI中,是无法指定serviceClass的,只能使用默认的SERVICE_CLASS_VOICE。但是,我们可以在这里通过SIB选择不同的serviceClass。在上述问题中,由于用户的Sim卡设置了SERVICE_CLASS_DATA_SYNC下的无条件呼叫转移,所以,根据以下代码,我们可以在MMI Code中,设置SIB的值为24(注意,SIB的值与CommandsInterface.java中定义的serviceClass的值并不一致),从而取消该呼叫转移的设置。
private static int
siToServiceClass(String si) {
if (si == null || si.length() == 0) {
return SERVICE_CLASS_NONE;
} else {
// NumberFormatException should cause MMI fail
int serviceCode = Integer.parseInt(si, 10);
switch (serviceCode) {
......
case 24: return SERVICE_CLASS_DATA_SYNC;
......
default:
throw new RuntimeException("unsupported MMI service code " + si);
}
}
}
那么也不难推出,用户可能是在不知道什么情况下,使用了下面的MMI Code,设置了serviceClass为SERVICE_CLASS_DATA_SYNC的无条件呼叫转移(其中13011112222为随意写的一个电话号码):
**21*13011112222*24# -> 按下拨号键
在我们自己的手机上尝试,打印出如下log:
// 使用**21*13011112222*24# 注册serviceClass为SERVICE_CLASS_DATA_SYNC的无条件呼叫转移,转移至13011112222
09-09 19:49:48.797 2125 2125 D GsmMmiCode: is CF setCallForward
09-09 19:49:48.797 2125 2125 D RILJ-0 : [4509]> SET_CALL_FORWARD 3 0 16 0
09-09 19:49:50.711 2125 2249 D RILJ-0 : [4509]< SET_CALL_FORWARD
// 查询到无条件呼叫转移为active状态,serviceClass为16,即SERVICE_CLASS_DATA_SYNC
09-09 19:50:20.030 2125 2125 D RILJ-0 : [4596]> QUERY_CALL_FORWARD_STATUS 0 0
09-09 19:50:21.480 2125 2249 D RILJ-0 : [4596]< QUERY_CALL_FORWARD_STATUS {[com.android.internal.telephony.CallForwardInfo@b26a24e active reason: 0 serviceClass: 16 0 seconds] }
09-09 19:50:21.506 2125 2125 D RILJ-0 : [4624]> QUERY_CALL_FORWARD_STATUS 1 0
09-09 19:50:23.488 2125 2249 D RILJ-0 : [4624]< QUERY_CALL_FORWARD_STATUS {[com.android.internal.telephony.CallForwardInfo@93338b not active reason: 1 serviceClass: 255 0 seconds] }
09-09 19:50:23.490 2125 2125 D RILJ-0 : [4653]> QUERY_CALL_FORWARD_STATUS 2 0
09-09 19:50:25.255 2125 2249 D RILJ-0 : [4653]< QUERY_CALL_FORWARD_STATUS {[com.android.internal.telephony.CallForwardInfo@3d0ad26 not active reason: 2 serviceClass: 255 0 seconds] }
09-09 19:50:25.257 2125 2125 D RILJ-0 : [4666]> QUERY_CALL_FORWARD_STATUS 3 0
09-09 19:50:27.080 2125 2249 D RILJ-0 : [4666]< QUERY_CALL_FORWARD_STATUS {[com.android.internal.telephony.CallForwardInfo@4447bbd not active reason: 3 serviceClass: 255 0 seconds] }
那么,想要取消也很容易,使用如下MMI Code即可:
##21**24# -> 按下拨号键
取消的log如下:
// 使用##21**24# 取消serviceClass为SERVICE_CLASS_DATA_SYNC的无条件呼叫转移
09-09 19:55:54.651 2125 2125 D RILJ-0 : [4186]> SET_CALL_FORWARD 4 0 16 0
09-09 19:55:56.202 2125 2232 D RILJ-0 : [4186]< SET_CALL_FORWARD
// 取消后,查询到无条件呼叫转移为not actice状态
09-09 19:56:04.390 2125 2125 D RILJ-0 : [4244]> QUERY_CALL_FORWARD_STATUS 0 0
09-09 19:56:05.965 2125 2232 D RILJ-0 : [4244]< QUERY_CALL_FORWARD_STATUS {[com.android.internal.telephony.CallForwardInfo@6c8456f not active reason: 0 serviceClass: 255 0 seconds] }
09-09 19:56:05.972 2125 2125 D RILJ-0 : [4251]> QUERY_CALL_FORWARD_STATUS 1 0
09-09 19:56:07.545 2125 2232 D RILJ-0 : [4251]< QUERY_CALL_FORWARD_STATUS {[com.android.internal.telephony.CallForwardInfo@ccdc35a not active reason: 1 serviceClass: 255 0 seconds] }
09-09 19:56:07.546 2125 2125 D RILJ-0 : [4264]> QUERY_CALL_FORWARD_STATUS 2 0
09-09 19:56:09.450 2125 2232 D RILJ-0 : [4264]< QUERY_CALL_FORWARD_STATUS {[com.android.internal.telephony.CallForwardInfo@2e75e81 not active reason: 2 serviceClass: 255 0 seconds] }
09-09 19:56:09.453 2125 2125 D RILJ-0 : [4277]> QUERY_CALL_FORWARD_STATUS 3 0
09-09 19:56:11.172 2125 2232 D RILJ-0 : [4277]< QUERY_CALL_FORWARD_STATUS {[com.android.internal.telephony.CallForwardInfo@2fdb514 not active reason: 3 serviceClass: 255 0 seconds] }
同理,呼叫等待和呼叫限制的设置/取消/查询方法,与呼叫转移类似,只要查询processCode()方法就能很容易的知道该如何使用MMI Code。大家可以举一反三,自己尝试一下,使用MMI Code来设置其他的补充业务。