MMI Code设置/查询 补充业务(Supplementary Service) 流程详解

转载请注明出处: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#",我们也说了,后面的"?"表示,他们可以出现零次或一次,所以,其实这里可以是如下的任意一种形式:

  •     *SIA*SIB*SIC*PWD_CONFIRM#
  •     *SIA*SIB*SIC#
  •     *SIA*SIB**PWD_CONFIRM#
  •     *SIA**SIC*PWD_CONFIRM#
  •     **SIB*SIC*PWD_CONFIRM#
  •     *SIA*SIB#
  •     *SIA**SIC#
  •     **SIB*SIC#
  •     ***SIC*PWD_CONFIRM#
  •     **SIB**PWD_CONFIRM#
  •     *SIA***PWD_CONFIRM#
  •     *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来设置其他的补充业务。

你可能感兴趣的:(Android,系统分析)