Uicc之CatService(原)

        CatService主要负责STK菜单的相关事宜,本节我们就来分析该对象。


一、CatService的创建过程


        在前面第二节中我们分析过,在UiccCard的更新过程中,会初始化CatService对象:
        @UiccCard.java
        public void update(Context c, CommandsInterface ci, IccCardStatus ics) {
            synchronized (mLock) {
                if (mUiccApplications.length > 0 && mUiccApplications[0] != null) {
                    //创建CatService
                    mCatService = CatService.getInstance(mCi, mContext, this);
                } else {
                    if (mCatService != null) {
                        mCatService.dispose();
                    }
                    mCatService = null;
                }
            }
        }
        然后看具体的初始化流程:
        @CatService.java
        public static CatService getInstance(CommandsInterface ci, Context context, UiccCard ic) {
            UiccCardApplication ca = null;
            IccFileHandler fh = null;
            IccRecords ir = null;
            if (ic != null) {
                //获取UiccCardApplication、IccFileHandler、IccRecords等对象
                ca = ic.getApplicationIndex(0);
                if (ca != null) {
                    fh = ca.getIccFileHandler();
                    ir = ca.getIccRecords();
                }
            }
            synchronized (sInstanceLock) {
                if (sInstance == null) {
                    if (ci == null || ca == null || ir == null || context == null || fh == null || ic == null) {
                        return null;
                    }
                    //创建CatService的消息处理线程
                    HandlerThread thread = new HandlerThread("Cat Telephony service");
                    thread.start();
                    //创建CatService实例对象
                    sInstance = new CatService(ci, ca, ir, context, fh, ic);
                } else if ((ir != null) && (mIccRecords != ir)) {
                    //CatService已经被创建过,只需要更新其监听器
                    if (mIccRecords != null) {
                        mIccRecords.unregisterForRecordsLoaded(sInstance);
                    }
                    if (mUiccApplication != null) {
                        mUiccApplication.unregisterForReady(sInstance);
                    }
                    mIccRecords = ir;
                    mUiccApplication = ca;
                    mIccRecords.registerForRecordsLoaded(sInstance, MSG_ID_ICC_RECORDS_LOADED, null);
                    mUiccApplication.registerForReady(sInstance, MSG_ID_SIM_READY, null);
                } else {
                    CatLog.d(sInstance, "Return current sInstance");
                }
                return sInstance;
            }
        }
        看他的构造函数:
        private CatService(CommandsInterface ci, UiccCardApplication ca, IccRecords ir, Context context, IccFileHandler fh, UiccCard ic) {
            if (ci == null || ca == null || ir == null || context == null || fh == null || ic == null) {
                throw new NullPointerException( "Service: Input parameters must not be null");
            }
            mCmdIf = ci;
            mContext = context;

            //获取RilMessageDecoder对象
            mMsgDecoder = RilMessageDecoder.getInstance(this, fh);
            //向RIL注册监听器
            mCmdIf.setOnCatSessionEnd(this, MSG_ID_SESSION_END, null);
            mCmdIf.setOnCatProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null);
            mCmdIf.setOnCatEvent(this, MSG_ID_EVENT_NOTIFY, null);
            mCmdIf.setOnCatCallSetUp(this, MSG_ID_CALL_SETUP, null);

            mIccRecords = ir;
            mUiccApplication = ca;
            //向UiccCardApplication对象注册SIM Ready的监听器
            //向IccRecords对象注册Icc Record Loaded的监听器
            mUiccApplication.registerForReady(this, MSG_ID_SIM_READY, null);
            mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null);

            mStkAppInstalled = isStkAppInstalled();
        }
        我们看到在CatService的初始化过程中主要完成了一下三个任务:
        1、获取RilMessageDecoder对象mMsgDecoder;
        2、向RIL注册相关的通知监听;

        3、向UiccCardApplication、IccRecords注册SIM卡和Record的监听;


二、CatService的消息机制


        在上面的CatService构造函数中我们看到,CatService注册了六个主要的监听器:
        1、MSG_ID_SESSION_END
        2、MSG_ID_PROACTIVE_COMMAND
        3、MSG_ID_RIL_MSG_DECODED
        4、MSG_ID_EVENT_NOTIFY
        5、MSG_ID_CALL_SETUP
        6、MSG_ID_SIM_READY
        7、MSG_ID_ICC_RECORDS_LOADED

        而这六个消息事件中,有四个是比较重要的,按照接收的先后顺序分别是:
        MSG_ID_SIM_READY            ----通知CatService,SIM卡已经就绪,需要CatService反馈是否就绪
        MSG_ID_PROACTIVE_COMMAND    ----CatService拿到SIM卡中的STK信息
        MSG_ID_RIL_MSG_DECODED      ----RilMessageDecoder解析完STK数据后给CatService发送通知
        MSG_ID_SESSION_END          ----RIL上传当前消息已经传输完毕

        下面我们主要介绍以上四个消息的处理流程。


2.1、MSG_ID_SIM_READY


        这是CatService接收到的第一个消息,接收到该消息就说明SIM卡已经处于Ready状态,此时CatService向RIL报告说明CatService已经运行,准备接受消息。下面来看详细流程:
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_ID_SIM_READY:
                    //向RIL发送消息
                    mCmdIf.reportStkServiceIsRunning(null);
                    break;
                default:
                    throw new AssertionError("Unrecognized CAT command: " + msg.what);
            }
        }

        此时的RIL将会向Modem发送RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING的消息,从而告诉Modem,CatService已经准备好接受STK的数据了。


2.2、MSG_ID_PROACTIVE_COMMAND


        Modem接收到CatService已经启动的消息后,就会上报MSG_ID_PROACTIVE_COMMAND的消息,该消息中包含了SIM卡中存储的STK信息,此时 CatService需要将这些信息解析并保存,用于构建STK菜单
        请注意, 如果SIM卡不支持STK业务,将接收不到该消息
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_ID_SESSION_END:
                case MSG_ID_PROACTIVE_COMMAND:
                case MSG_ID_EVENT_NOTIFY:
                case MSG_ID_REFRESH:
                    String data = null;
                    if (msg.obj != null) {
                        AsyncResult ar = (AsyncResult) msg.obj;
                        if (ar != null && ar.result != null) {
                            try {
                                data = (String) ar.result;
                            } catch (ClassCastException e) {
                                break;
                            }
                        }
                    }
                    //通过RilMessageDecoder去解析数据
                    mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data));
                    break;
                default:
                    throw new AssertionError("Unrecognized CAT command: " + msg.what);
            }
        }
        我们看到,对于拿到的数据时通过mMsgDecoder去解析的,也就是RilMessageDecoder的对象,这个对象是在CatService的构造函数中被创建的。我们来看具体的解析数据的过程:
        @RilMessageDecoder.java
        public void sendStartDecodingMessageParams(RilMessage rilMsg) {
            Message msg = obtainMessage(CMD_START);
            msg.obj = rilMsg;
            sendMessage(msg);
        }
        在RilMessageDecoder中又将消息封装后发送给自己,并在StateStart中被处理:
        private class StateStart extends State {
            @Override
            public boolean processMessage(Message msg) {
                if (msg.what == CMD_START) {
                    if (decodeMessageParams((RilMessage)msg.obj)) {
                        transitionTo(mStateCmdParamsReady);
                    }
                } else {
                    CatLog.d(this, "StateStart unexpected expecting START=" + CMD_START + " got " + msg.what);
                }
                return true;
            }
        }
        然后在decodeMessageParams()方法中被解析:
        private boolean decodeMessageParams(RilMessage rilMsg) {
            boolean decodingStarted;
            mCurrentRilMessage = rilMsg;
            switch(rilMsg.mId) {
                case CatService.MSG_ID_PROACTIVE_COMMAND:
                case CatService.MSG_ID_EVENT_NOTIFY:
                case CatService.MSG_ID_REFRESH:
                    byte[] rawData = null;
                    try {
                        rawData = IccUtils.hexStringToBytes((String) rilMsg.mData);
                    } catch (Exception e) {
                    }
                    try {
                        // Start asynch parsing of the command parameters.
                        mCmdParamsFactory.make(BerTlv.decode(rawData));
                        decodingStarted = true;
                    } catch (ResultException e) {
                    }
                    break;
                default:
                    decodingStarted = false;
                    break;
            }
            return decodingStarted;
        }
        在decodeMessageParams()中先通过hexStringToBytes()方法将得到的Modem数据转换为byte类型的数组数据rawData,然后将此数据交给CommandParamsFactory类的make()方法去解析,经过make的解析就可以得到每项STK菜单的图标、菜单文本以及需要的控件等信息,其解析过程为:
        @CommandParamsFactory.java
        void make(BerTlv berTlv) {
            mCmdParams = null;
            mIconLoadState = LOAD_NO_ICON;
            boolean cmdPending = false;
            List<ComprehensionTlv> ctlvs = berTlv.getComprehensionTlvs();
            CommandDetails cmdDet = processCommandDetails(ctlvs);

            //得到当前的命令类型
            AppInterface.CommandType cmdType = AppInterface.CommandType .fromInt(cmdDet.typeOfCommand);
            try {
                switch (cmdType) {
                    case SET_UP_MENU:
                        cmdPending = processSelectItem(cmdDet, ctlvs);
                        break;
                    case SELECT_ITEM:
                        cmdPending = processSelectItem(cmdDet, ctlvs);
                        break;
                    case DISPLAY_TEXT:
                        cmdPending = processDisplayText(cmdDet, ctlvs);
                        break;
                    case SET_UP_IDLE_MODE_TEXT:
                        cmdPending = processSetUpIdleModeText(cmdDet, ctlvs);
                        break;
                    case GET_INKEY:
                        cmdPending = processGetInkey(cmdDet, ctlvs);
                        break;
                    case GET_INPUT:
                        cmdPending = processGetInput(cmdDet, ctlvs);
                        break;
                    case SEND_DTMF:
                    case SEND_SMS:
                    case SEND_SS:
                    case SEND_USSD:
                        cmdPending = processEventNotify(cmdDet, ctlvs);
                        break;
                    case GET_CHANNEL_STATUS:
                    case SET_UP_CALL:
                        cmdPending = processSetupCall(cmdDet, ctlvs);
                        break;
                    case REFRESH:
                        processRefresh(cmdDet, ctlvs);
                        cmdPending = false;
                        break;
                    case LAUNCH_BROWSER:
                        cmdPending = processLaunchBrowser(cmdDet, ctlvs);
                        break;
                    case PLAY_TONE:
                        cmdPending = processPlayTone(cmdDet, ctlvs);
                        break;
                    case PROVIDE_LOCAL_INFORMATION:
                        cmdPending = processProvideLocalInfo(cmdDet, ctlvs);
                        break;
                    case OPEN_CHANNEL:
                    case CLOSE_CHANNEL:
                    case RECEIVE_DATA:
                    case SEND_DATA:
                        cmdPending = processBIPClient(cmdDet, ctlvs);
                        break;
                    default:
                        // unsupported proactive commands
                        mCmdParams = new CommandParams(cmdDet);
                        sendCmdParams(ResultCode.BEYOND_TERMINAL_CAPABILITY);
                        return;
                }
            } catch (ResultException e) {
            }
            if (!cmdPending) {
                sendCmdParams(ResultCode.OK);
            }
        }
        从make()方法看出,解析的主要类型分为以下几类:
        1、SET_UP_MENU      ---开机完成后初始化STK菜单项,而且是根目录菜单,子菜单项是在发生SELECT_ITEM后解析的
        2、SELECT_ITEM      ---点击STK菜单项后,传递二级目录的数据
        3、DISPLAY_TEXT     ---弹出文本提示框,比如开机的运营商问候语
        4、GET_INPUT        ---弹出编辑框,比如编辑群发短信
        5、SEND_SMS         ---发送短消息给运行商

        我们分别来看以上几个事件的处理流程:


2.2.1、SET_UP_MENU


        这个消息用于搭建STK的菜单项。其处理是在processSelectItem()中发生的:
        private boolean processSelectItem(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs) throws ResultException {
            Menu menu = new Menu();
            IconId titleIconId = null;
            ItemsIconId itemsIconId = null;
            Iterator<ComprehensionTlv> iter = ctlvs.iterator();

            //搜索并解析该STK应用的名字,比如“神州行天地”
            ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
            if (ctlv != null) {
                menu.title = ValueParser.retrieveAlphaId(ctlv);
            }

            //循环解析该STK应用的所有根目录菜单
            //比如对于神州行的卡,就会有“轻松问候”、“短信群发”、“优惠快讯”、“业务精选”等菜单
            while (true) {
                ctlv = searchForNextTag(ComprehensionTlvTag.ITEM, iter);
                if (ctlv != null) {
                    menu.items.add(ValueParser.retrieveItem(ctlv));
                } else {
                    break;
                }
            }

            if (menu.items.size() == 0) {
                throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
            }

            //搜索ITEM_ID
            ctlv = searchForTag(ComprehensionTlvTag.ITEM_ID, ctlvs);
            if (ctlv != null) {
                menu.defaultItem = ValueParser.retrieveItemId(ctlv) - 1;
            }

            //搜索ICON_ID
            ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
            if (ctlv != null) {
                mIconLoadState = LOAD_SINGLE_ICON;
                titleIconId = ValueParser.retrieveIconId(ctlv);
                menu.titleIconSelfExplanatory = titleIconId.selfExplanatory;
            }

            ctlv = searchForTag(ComprehensionTlvTag.ITEM_ICON_ID_LIST, ctlvs);
            if (ctlv != null) {
                mIconLoadState = LOAD_MULTI_ICONS;
                itemsIconId = ValueParser.retrieveItemsIconId(ctlv);
                menu.itemsIconSelfExplanatory = itemsIconId.selfExplanatory;
            }

            boolean presentTypeSpecified = (cmdDet.commandQualifier & 0x01) != 0;
            if (presentTypeSpecified) {
                if ((cmdDet.commandQualifier & 0x02) == 0) {
                    menu.presentationType = PresentationType.DATA_VALUES;
                } else {
                    menu.presentationType = PresentationType.NAVIGATION_OPTIONS;
                }
            }
            menu.softKeyPreferred = (cmdDet.commandQualifier & 0x04) != 0;
            menu.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;

            //用解析的菜单项构建mCmdParams对象
            mCmdParams = new SelectItemParams(cmdDet, menu, titleIconId != null);

            //处理图标的载入
            switch(mIconLoadState) {
                case LOAD_NO_ICON:
                    return false;
                case LOAD_SINGLE_ICON:
                    mIconLoader.loadIcon(titleIconId.recordNumber, this .obtainMessage(MSG_ID_LOAD_ICON_DONE));
                    break;
                case LOAD_MULTI_ICONS:
                    int[] recordNumbers = itemsIconId.recordNumbers;
                    if (titleIconId != null) {
                        // Create a new array for all the icons (title and items).
                        recordNumbers = new int[itemsIconId.recordNumbers.length + 1];
                        recordNumbers[0] = titleIconId.recordNumber;
                        System.arraycopy(itemsIconId.recordNumbers, 0, recordNumbers, 1, itemsIconId.recordNumbers.length);
                    }
                    mIconLoader.loadIcons(recordNumbers, this .obtainMessage(MSG_ID_LOAD_ICON_DONE));
                    break;
            }
            return true;
        }

        在上面这个过程中,processSelectItem()会对STK的菜单项的文本、ID、图标等进行解析,最终把解析完的数据放入mCmdParams中,并通过CatService把数据发送给StkAppService并显示相应的菜单项。


2.2.2、SELECT_ITEM


        SELECT_ITEM的事件也是通过processSelectItem()来处理的,他的主要作用就是当用户点击某个STK菜单时,对下一级菜单进行解析,并在StkAppService中进行显示。


2.2.3、DISPLAY_TEXT


        DISPLAY_TEXT的作用主要是显示一个提示框,包括开机后的运营商欢迎提醒。
        private boolean processDisplayText(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs) throws ResultException {
            TextMessage textMsg = new TextMessage();
            IconId iconId = null;

            //得到显示的文本,比如开机后的运营商欢迎语
            ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING, ctlvs);
            if (ctlv != null) {
                textMsg.text = ValueParser.retrieveTextString(ctlv);
            }
            if (textMsg.text == null) {
                throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
            }

            ctlv = searchForTag(ComprehensionTlvTag.IMMEDIATE_RESPONSE, ctlvs);
            if (ctlv != null) {
                textMsg.responseNeeded = false;
            }
            //图标
            ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
            if (ctlv != null) {
                iconId = ValueParser.retrieveIconId(ctlv);
                textMsg.iconSelfExplanatory = iconId.selfExplanatory;
            }
            //显示的持续时间
            ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
            if (ctlv != null) {
                textMsg.duration = ValueParser.retrieveDuration(ctlv);
            }

            textMsg.isHighPriority = (cmdDet.commandQualifier & 0x01) != 0;
            textMsg.userClear = (cmdDet.commandQualifier & 0x80) != 0;

            //构建mCmdParams
            mCmdParams = new DisplayTextParams(cmdDet, textMsg);
            if (iconId != null) {
                mIconLoadState = LOAD_SINGLE_ICON;
                mIconLoader.loadIcon(iconId.recordNumber, this .obtainMessage(MSG_ID_LOAD_ICON_DONE));
                return true;
            }
            return false;
        }

        我们看到,在processDisplayText()的过程中,主要还是对要显示数据进行解析,得到显示的文本和图标,最终将解析结果放入mCmdParams中并发送给StkAppService。


2.2.4、GET_INPUT


        当某个STK菜单需要弹出编辑框时,比如“群发短信”的功能,就会通过processGetInput()构建编辑框。
        private boolean processGetInput(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs) throws ResultException {
            Input input = new Input();
            IconId iconId = null;

            //要显示的提示字串,比如“输入内容”
            ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING, ctlvs);
            if (ctlv != null) {
                input.text = ValueParser.retrieveTextString(ctlv);
            } else {
                throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
            }

            //得到输入框的输入最大长度
            ctlv = searchForTag(ComprehensionTlvTag.RESPONSE_LENGTH, ctlvs);
            if (ctlv != null) {
                try {
                    byte[] rawValue = ctlv.getRawValue();
                    int valueIndex = ctlv.getValueIndex();
                    input.minLen = rawValue[valueIndex] & 0xff;
                    input.maxLen = rawValue[valueIndex + 1] & 0xff;
                } catch (IndexOutOfBoundsException e) {
                    throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
                }
            } else {
                throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
            }

            ctlv = searchForTag(ComprehensionTlvTag.DEFAULT_TEXT, ctlvs);
            if (ctlv != null) {
                input.defaultText = ValueParser.retrieveTextString(ctlv);
            }
            // parse icon identifier
            ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
            if (ctlv != null) {
                iconId = ValueParser.retrieveIconId(ctlv);
            }

            input.digitOnly = (cmdDet.commandQualifier & 0x01) == 0;
            input.ucs2 = (cmdDet.commandQualifier & 0x02) != 0;
            input.echo = (cmdDet.commandQualifier & 0x04) == 0;
            input.packed = (cmdDet.commandQualifier & 0x08) != 0;
            input.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;

            mCmdParams = new GetInputParams(cmdDet, input);

            if (iconId != null) {
                mIconLoadState = LOAD_SINGLE_ICON;
                mIconLoader.loadIcon(iconId.recordNumber, this .obtainMessage(MSG_ID_LOAD_ICON_DONE));
                return true;
            }
            return false;
        }

        和上面两个消息类似,最终会把解析得到的编辑框信息发送给StkAppService来显示。


2.2.5、SEND_SMS


        当通过STK向运营商或者其他用户发送短信时, RIL就会向CatService发送SEND_SMS的消息,此时CatService要做的就是通知StkAppService去显示一个Toast框提示“正在发送信息”(而不是去发送信息)
        private boolean processEventNotify(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs) throws ResultException {
            TextMessage textMsg = new TextMessage();
            IconId iconId = null;

            //发送短信时的提示语,比如“正在发送消息…”
            ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
            textMsg.text = ValueParser.retrieveAlphaId(ctlv);

            ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
            if (ctlv != null) {
                iconId = ValueParser.retrieveIconId(ctlv);
                textMsg.iconSelfExplanatory = iconId.selfExplanatory;
            }

            textMsg.responseNeeded = false;
            mCmdParams = new DisplayTextParams(cmdDet, textMsg);
            if (iconId != null) {
                mIconLoadState = LOAD_SINGLE_ICON;
                mIconLoader.loadIcon(iconId.recordNumber, this .obtainMessage(MSG_ID_LOAD_ICON_DONE));
                return true;
            }
            return false;
        }

        和上面的消息类似,最终会把解析得到的信息发送给StkAppService来显示。


2.2.6、CommandParamsFactory作用


        然后回到make()方法中,将解析的结果发送给RilMessageDecoder对象:
        void make(BerTlv berTlv) {
            try {
                switch (cmdType) {
                    case SET_UP_MENU:
                    case SELECT_ITEM:
                    case DISPLAY_TEXT:
                    case SET_UP_IDLE_MODE_TEXT:
                    case GET_INKEY:
                    case GET_INPUT:
                    case SEND_DTMF:
                    case SEND_SMS:
                    case SEND_SS:
                    case SEND_USSD:
                    case GET_CHANNEL_STATUS:
                    case SET_UP_CALL:
                    case REFRESH:
                    case LAUNCH_BROWSER:
                    case PLAY_TONE:
                    case PROVIDE_LOCAL_INFORMATION:
                    case OPEN_CHANNEL:
                    case CLOSE_CHANNEL:
                    case RECEIVE_DATA:
                    case SEND_DATA:
                    default:
                }
            } catch (ResultException e) {
            }
            if (!cmdPending) {
                //将解析的mCmdParams发送给RilMessageDecoder
                sendCmdParams(ResultCode.OK);
            }
        }
        private void sendCmdParams(ResultCode resCode) {
            mCaller.sendMsgParamsDecoded(resCode, mCmdParams);
        }
        这里的mCaller就是CommandParamsFactory初始化时传递进来的RilMessageDecoder:
        @RilMessageDecoder.java
        public void sendMsgParamsDecoded(ResultCode resCode, CommandParams cmdParams) {
            Message msg = obtainMessage(RilMessageDecoder.CMD_PARAMS_READY);
            msg.arg1 = resCode.value();
            msg.obj = cmdParams;
            sendMessage(msg);
        }
        至此,CommandParamsFactory的任务已经完成, 其主要作用体现在:1、解析SIM卡上报的STK数据;2、将解析后的数据发送给RilMessageDecoder
        接下来就 需要RilMessageDecoder将解析的结果发送给CatService
        当RilMessageDecoder通过sendMsgParamsDecoded()将消息发送出去后,经过其父类StateMachine的处理,再次由RilMessageDecoder接收到消息:
        private class StateCmdParamsReady extends State {
            @Override
            public boolean processMessage(Message msg) {
                if (msg.what == CMD_PARAMS_READY) {
                    //接收到消息
                    mCurrentRilMessage.mResCode = ResultCode.fromInt(msg.arg1);
                    mCurrentRilMessage.mData = msg.obj;
                    sendCmdForExecution(mCurrentRilMessage);
                    transitionTo(mStateStart);
                } else {
                    CatLog.d(this, "StateCmdParamsReady expecting CMD_PARAMS_READY=" + CMD_PARAMS_READY + " got " + msg.what);
                    deferMessage(msg);
                }
                return true;
            }
        }
        然后通过sendCmdForExecution()把解析结果重新发送给CatService:
        private void sendCmdForExecution(RilMessage rilMsg) {
            Message msg = mCaller.obtainMessage(CatService.MSG_ID_RIL_MSG_DECODED,
                    new RilMessage(rilMsg));
            msg.sendToTarget();
        }

        然后在CatService中将会收到MSG_ID_RIL_MSG_DECODED的消息。


2.3、MSG_ID_RIL_MSG_DECODED


        这个消息并不是从Ril上传上来的,而是在6.2.1中,对MSG_ID_PROACTIVE_COMMAND消息处理之后,由RilMessageDecoder发送过来的,它标志着刚才从Modem接收到的数据已经被解析完毕。而这个消息要做的任务就是将解析后的数据传递给RIL和StkAppService.java。
        下面来看该消息的处理流程:
        @CatService.java
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_ID_RIL_MSG_DECODED:
                    handleRilMsg((RilMessage) msg.obj);
                    break; 
                default:
                    throw new AssertionError("Unrecognized CAT command: " + msg.what);
            }
        }
        接着往下看:
        private void handleRilMsg(RilMessage rilMsg) {
            CommandParams cmdParams = null;
            switch (rilMsg.mId) {
                case MSG_ID_PROACTIVE_COMMAND:
                    try {
                        //拿到解析后的数据
                        cmdParams = (CommandParams) rilMsg.mData;
                    } catch (ClassCastException e) {
                    }
                    if (cmdParams != null) {
                        if (rilMsg.mResCode == ResultCode.OK) {
                            //处理当前的数据
                            handleCommand(cmdParams, true);
                        } else {
                            sendTerminalResponse(cmdParams.mCmdDet, rilMsg.mResCode, false, 0, null);
                        }
                    }
                    break;
            }
        }
        private void handleCommand(CommandParams cmdParams, boolean isProactiveCmd) {
            CharSequence message;
            CatCmdMessage cmdMsg = new CatCmdMessage(cmdParams);
            switch (cmdParams.getCommandType()) {
                case SET_UP_MENU:
                    if (removeMenu(cmdMsg.getMenu())) {
                        mMenuCmd = null;
                    } else {
                        mMenuCmd = cmdMsg;
                    }
                    sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null);
                    break;
                case DISPLAY_TEXT:
                    if (!cmdMsg.geTextMessage().responseNeeded) {
                        sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null);
                    }
                    break;
                case SELECT_ITEM:
                case GET_INPUT:
                case GET_INKEY:
                    break;
                case SEND_DTMF:
                case SEND_SMS:
                case SEND_SS:
                case SEND_USSD:
                    if ((((DisplayTextParams)cmdParams).mTextMsg.text != null) && (((DisplayTextParams)cmdParams).mTextMsg.text.equals(STK_DEFAULT))) {
                        message = mContext.getText(com.android.internal.R.string.sending);
                        ((DisplayTextParams)cmdParams).mTextMsg.text = message.toString();
                    }
                    break;
                case PLAY_TONE:
                    break;
                case SET_UP_CALL:
                    if ((((CallSetupParams) cmdParams).mConfirmMsg.text != null) && (((CallSetupParams) cmdParams).mConfirmMsg.text.equals(STK_DEFAULT))) {
                        message = mContext.getText(com.android.internal.R.string.SetupCallDefault);
                        ((CallSetupParams) cmdParams).mConfirmMsg.text = message.toString();
                    }
                    break;
                default:
                    CatLog.d(this, "Unsupported command");
                    return;
            }
            mCurrntCmd = cmdMsg;
            Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
            intent.putExtra("STK CMD", cmdMsg);
            mContext.sendBroadcast(intent);
        }

        我们看到,在handleCommand()中,对于主要的几种数据类型,比如SET_UP_MENU、DISPLAY_TEXT的最后都有sendTerminalResponse()的操作,而这个操作的作用就是将数据封装后发送给RIL。并且在handleCommand()的最后,通过广播的形式将当前解析的结果发送出来,发送之后就有STK应用的StkAppService.java负责接收,并构建STK菜单。


2.4、MSG_ID_SESSION_END


        这个消息主要是RIL用于告诉CatService,当前的会话已经传输完毕。
        和MSG_ID_PROACTIVE_COMMAND的消息流程类似,只是更加的简单:
        @CatService.java
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_ID_SESSION_END:
                case MSG_ID_PROACTIVE_COMMAND:
                case MSG_ID_EVENT_NOTIFY:
                case MSG_ID_REFRESH:
                    CatLog.d(this, "ril message arrived");
                    String data = null;
                    if (msg.obj != null) {
                        AsyncResult ar = (AsyncResult) msg.obj;
                        if (ar != null && ar.result != null) {
                            try {
                                data = (String) ar.result;
                            } catch (ClassCastException e) {
                                break;
                            }
                        }
                    }
                    //让RilMessageDecoder去处理
                    mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data));
                    break;
            }
        }
        然后就交给RilMessageDecoder中的decodeMessageParams()处理:
        private boolean decodeMessageParams(RilMessage rilMsg) {
            boolean decodingStarted;
            mCurrentRilMessage = rilMsg;
            switch(rilMsg.mId) {
                case CatService.MSG_ID_SESSION_END:
                case CatService.MSG_ID_CALL_SETUP:
                    mCurrentRilMessage.mResCode = ResultCode.OK;
                    sendCmdForExecution(mCurrentRilMessage);
                    decodingStarted = false;
                    break;
                default:
                    decodingStarted = false;
                    break;
            }
            return decodingStarted;
        }
        这里和MSG_ID_PROACTIVE_COMMAND的区别就是,不再进入CommandParamsFactory中解析数据,而是直接通过sendCmdForExecution()将OK的ResultCode发回给CatService,并且带回的返回码也是MSG_ID_RIL_MSG_DECODED消息。

        接下来的流程和MSG_ID_PROACTIVE_COMMAND相同,就是将结果发送给RIL和StkAppService.java。


三、CatService的主要作用


        现在,我们可以来简要分析CatService的作用了。

        从以上的分析中得知, CatService的作用主要体现在接收并解析RIL层发来的STK相关原始数据,并把解析后的数据同时传回给RIL和发送给StkAppService

        但是我们发现,CatService虽然对数据进行了解析,但是并没有显示菜单、弹出提示框的动作,而真正将数据转化为控件是在StkAppService中完成的,这是一个上层的app,主要作用就是拿到CatService解析的数据并进行显示,以及处理用户在相应菜单上进行的点击操作。


四、StkAppService


        在前面的2.3中我们知道,当CatService完成RIL上报的数据解析后,就会收到MSG_ID_RIL_MSG_DECODED消息,然后CatService就会将解析的结果发送给RIL,同时发送Intent出来:

        Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
        intent.putExtra("STK CMD", cmdMsg);
        mContext.sendBroadcast(intent);
        这里发送的Intent,就会被STK模块的StkCmdReceiver接收到,而且该Intent中的"STK CMD"中存放的就是当前的解析结果。
        @StkCmdReceiver.java
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(AppInterface.CAT_CMD_ACTION)) {
                handleCommandMessage(context, intent);
            } else if (action.equals(AppInterface.CAT_SESSION_END_ACTION)) {
                handleSessionEnd(context, intent);
            }
        }
        然后就会在handleCommandMessage()中启动StkAppService:
        private void handleCommandMessage(Context context, Intent intent) {
            Bundle args = new Bundle();
            args.putInt(StkAppService.OPCODE, StkAppService.OP_CMD);
            args.putParcelable(StkAppService.CMD_MSG, intent .getParcelableExtra("STK CMD"));
            context.startService(new Intent(context, StkAppService.class) .putExtras(args));
        }
        请注意,在startService时,args里面放了两个数据:1、StkAppService.OPCODE里面存的是StkAppService.OP_CMD;2、StkAppService.CMD_MSG里面存的是CatService解析获取的数据,比如是SET_UP_MENU还是DISPLAY_TEXT等。
        在StkAppService的onStart()时,将会从Intent中获取OPCODE中的数据,也就是OP_CMD,然后把从CatService传递下来的CatCmdMessage数据放入message中,发送给mServiceHandler:
        @StkAppService.java
        public void onStart(Intent intent, int startId) {
            mStkService = com.android.internal.telephony.cat.CatService .getInstance();
            if (mStkService == null) {
            }
            Bundle args = intent.getExtras();
            if (args == null) {
                return;
            }

            Message msg = mServiceHandler.obtainMessage();
            msg.arg1 = args.getInt(OPCODE);
            switch(msg.arg1) {
                case OP_CMD:
                    msg.obj = args.getParcelable(CMD_MSG);
                    break;
                case OP_RESPONSE:
                    msg.obj = args;
                case OP_LAUNCH_APP:
                case OP_END_SESSION:
                case OP_BOOT_COMPLETED:
                    break;
                default:
                    return;
            }
            mServiceHandler.sendMessage(msg);
        }
        然后在ServiceHandler中处理:
        private final class ServiceHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                int opcode = msg.arg1;
                switch (opcode) {
                    case OP_LAUNCH_APP:
                        break;
                    case OP_CMD:
                        CatCmdMessage cmdMsg = (CatCmdMessage) msg.obj;
                        if (!isCmdInteractive(cmdMsg)) {
                            handleCmd(cmdMsg);
                        } else {
                            if (!mCmdInProgress) {
                                mCmdInProgress = true;
                                handleCmd((CatCmdMessage) msg.obj);
                            } else {
                                mCmdsQ.addLast(new DelayedCmd(OP_CMD, (CatCmdMessage) msg.obj));
                            }
                        }
                        break;
                    case OP_RESPONSE:
                        break;
                    case OP_END_SESSION:
                        break;
                    case OP_BOOT_COMPLETED:
                        break;
                    case OP_DELAYED_MSG:
                        break;
                }
            }
        }
        由于当前的opcode是OP_CMD,那么在该case中进行其他的处理,这里会根据isCmdInteractive()来判断当前的请求,不过对于SET_UP_MENU、DISPLAY_TEXT等消息,最终还是要通过handleCmd()去处理:
        private void handleCmd(CatCmdMessage cmdMsg) {
            if (cmdMsg == null) {
                return;
            }
            switch (cmdMsg.getCmdType()) {
                case DISPLAY_TEXT:
                    //弹出提示框
                    TextMessage msg = cmdMsg.geTextMessage();
                    responseNeeded = msg.responseNeeded;
                    waitForUsersResponse = msg.responseNeeded;
                    if (lastSelectedItem != null) {
                        msg.title = lastSelectedItem;
                    } else if (mMainCmd != null){
                        msg.title = mMainCmd.getMenu().title;
                    } else {
                        msg.title = "";
                    }
                    launchTextDialog();
                    break;
                case SELECT_ITEM:
                    //载入下一级菜单
                    mCurrentMenu = cmdMsg.getMenu();
                    launchMenuActivity(cmdMsg.getMenu());
                    break;
                case SET_UP_MENU:
                    //显示列表
                    mMainCmd = mCurrentCmd;
                    mCurrentMenu = cmdMsg.getMenu();
                    if (removeMenu()) {
                        CatLog.d(this, "Uninstall App");
                        mCurrentMenu = null;
                        StkAppInstaller.unInstall(mContext);
                    } else {
                        CatLog.d(this, "Install App");
                        StkAppInstaller.install(mContext);
                    }
                    if (mMenuIsVisibile) {
                        launchMenuActivity(null);
                    }
                    break;
                case GET_INPUT:
                case GET_INKEY:
                    //显示输入框
                    launchInputActivity();
                    break;
                case SET_UP_IDLE_MODE_TEXT:
                    waitForUsersResponse = false;
                    launchIdleText();
                    break;
                case SEND_DTMF:
                case SEND_SMS:
                case SEND_SS:
                case SEND_USSD:
                    //发送信息的提示
                    waitForUsersResponse = false;
                    launchEventMessage();
                    break;
                case LAUNCH_BROWSER:
                    //载入浏览器
                    launchConfirmationDialog(mCurrentCmd.geTextMessage());
                    break;
                case SET_UP_CALL:
                    //呼叫
                    launchConfirmationDialog(mCurrentCmd.getCallSettings().confirmMsg);
                    break;
                case PLAY_TONE:
                    //播放铃声
                    launchToneDialog();
                    break;
                case OPEN_CHANNEL:
                    launchOpenChannelDialog();
                    break;
                case CLOSE_CHANNEL:
                case RECEIVE_DATA:
                case SEND_DATA:
                    TextMessage m = mCurrentCmd.geTextMessage();
                    if ((m != null) && (m.text == null)) {
                        switch(cmdMsg.getCmdType()) {
                            case CLOSE_CHANNEL:
                                m.text = getResources().getString(R.string.default_close_channel_msg);
                                break;
                            case RECEIVE_DATA:
                                m.text = getResources().getString(R.string.default_receive_data_msg);
                                break;
                            case SEND_DATA:
                                m.text = getResources().getString(R.string.default_send_data_msg);
                                break;
                        }
                    }
                    launchEventMessage();
                    break;
            }

            if (!waitForUsersResponse) {
                if (mCmdsQ.size() != 0) {
                    callDelayedMsg();
                } else {
                    mCmdInProgress = false;
                }
            }
        }
        接下来,就会对当前的请求做UI层的处理,比如当需要弹出对话框时(DISPLAY_TEXT)就会通过launchTextDialog()方法弹出对话框,当需要弹出菜单时(SELECT_ITEM/SET_UP_MENU)就会通过launchMenuActivity()方法来实现,当需要一个编辑框(GET_INPUT)时,就会通过launchInputActivity()来实现。
        至此,Uicc相关的知识总结完毕。

你可能感兴趣的:(源码,android,框架,telephony,Uicc)