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

一、CatService的创建过程

在前面第二节中我们分析过,在UiccCard的更新过程中,会初始化CatService对象:

        @UiccCard.javapublic void update(Context c, CommandsInterface ci, IccCardStatus ics) {synchronized (mLock) {if (mUiccApplications.length > 0 && mUiccApplications[0] != null) {//创建CatServicemCatService = CatService.getInstance(mCi, mContext, this);} else {if (mCatService != null) {mCatService.dispose();}mCatService = null;}}}

然后看具体的初始化流程:

        @CatService.javapublic 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.javapublic void sendStartDecodingMessageParams(RilMessage rilMsg) {Message msg = obtainMessage(CMD_START);msg.obj = rilMsg;sendMessage(msg);}

在RilMessageDecoder中又将消息封装后发送给自己,并在StateStart中被处理:

        private class StateStart extends State {@Overridepublic 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.javavoid 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 commandsmCmdParams = 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_IDctlv = searchForTag(ComprehensionTlvTag.ITEM_ID, ctlvs);if (ctlv != null) {menu.defaultItem = ValueParser.retrieveItemId(ctlv) - 1;}//搜索ICON_IDctlv = 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;//构建mCmdParamsmCmdParams = 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 identifierctlv = 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发送给RilMessageDecodersendCmdParams(ResultCode.OK);}}private void sendCmdParams(ResultCode resCode) {mCaller.sendMsgParamsDecoded(resCode, mCmdParams);}

这里的mCaller就是CommandParamsFactory初始化时传递进来的RilMessageDecoder:

        @RilMessageDecoder.javapublic 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 {@Overridepublic 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.javapublic 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.javapublic 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.javapublic 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.javapublic 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 {@Overridepublic 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相关的知识总结完毕。

Uicc之CatService(原)相关推荐

  1. Uicc之CatService

    在前面第二节中我们分析过,在UiccCard的更新过程中,会初始化CatService对象: [java] view plaincopy @UiccCard.java public void upda ...

  2. UICC 之 USIM 详解全系列——Commands与Responses结构

    本人就职于国际知名终端厂商,负责modem芯片研发. 在5G早期负责终端数据业务层.核心网相关的开发工作,目前牵头6G算力网络技术标准研究. 文章目录 Commands与Responses结构 Com ...

  3. 什么是原码、反码、补码?什么是按位与?范围数字按位与!

    前言:学过计算机基础的大家都知道什么是二进制,什么是"与"运算,这里先给大家复习一下. 举一个简单的例子: 5的二进制表示是0101(补齐4位) 7的二进制表示是0111(补齐4位 ...

  4. Go 学习笔记(18)— 函数(04)[闭包定义、闭包修改变量、闭包记忆效应、闭包实现生成器、闭包复制原对象指针]

    1. 闭包定义 Go 语言中闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量,因此,简单的说: 函数 + ...

  5. [原]SSL 开发简述(Delphi)

    一.            简介 现在网上有关SSL的资料较多的是基于VC开发,Delphi的SSL开发资源很少. 本文主要使用OpenSSL为基础,讲述SSL的有关开发流程.OpenSSL功能非常丰 ...

  6. unigui中弹出对话框原窗体是没有了_最前线 | 微信对话框“搜一搜”功能上线,独辟蹊径的腾讯打着什么算盘?...

    更新界的"劳模"微信又出新花样了.9月9日,微信在对话框全量上线了搜一搜功能.简单来说,就是用户在微信对话过程中,如果遇到知识盲区,可以通过长按对话框文本,选择导航栏中的" ...

  7. 关于计算机中二进制原码,反码,补码的简要解释

    原码,补码,反码的概念 正数原码:正数的原码为取绝对值的数转二进制,5的原码为   00000000   00000000   00000000    00000101 负数原码:负数的原码为取绝对值 ...

  8. 恍然大悟之原、反、补

    曾在很多书籍中看到过关于原码.反码和补码的介绍,都未能够深入理解.今在Richard Blum编写的<汇编语言程序设计>一书中读到关于带符号整数一节的讲解时(确切的说是其中的一句话),的确 ...

  9. atomic原子类实现机制_JUC学习笔记--Atomic原子类

    Atomic 原子操作类包 Atomic包 主要是在多线程环境下,无锁的进行原子操作.核心操作是基于UnSafe类实现的CAS方法 CAS CAS: compareAndSwap :传入两个值:期望值 ...

最新文章

  1. 【java】java内存模型 (1)--基础
  2. 【新概念第一册】Lesson_29 Come in,Amy.
  3. WordCount by C# 结对编程
  4. AlertBox 弹出层(信息提示框)效果
  5. html5自动添加数据库,HTML5本地存储之如果没有数据库究竟会怎样
  6. 实用知识点梳理:网络传输介质、以太网、VLAN、HDLC、奈奎斯特定理与香农定理
  7. centos开发环境安装的备忘
  8. 聚类算法——Birch详解
  9. java 复合_【福利】java新手做的复合型计算器!
  10. Python高级——多进程(multiprocessing)
  11. 你做过的第一次事情有什么有趣的事吗?
  12. Bailian4121 股票买卖【最值】
  13. Eclipse(PHP、JAVA)的快捷键大全
  14. html文件能放病毒吗,建立一个防病毒/防复制/任何程序都无法删除的文件或文件夹...
  15. Android Studio导入安卓源码
  16. NXP KV30 SDK使用心得
  17. 编程求球的体积和表面积(c语言)
  18. 【Excel】设置数据有效性以及COUNTIF用法
  19. 世界各地 史上最全最详细无线通信频率分配表(内容含概wifi、2.4G、5G,绝对值得收藏)
  20. in-place运算总结

热门文章

  1. Python实现性能测试(locust)
  2. 时序分析creat_clock约束命令
  3. 高等数学(空间解析几何)
  4. Filter Listener——概述、应用、Filter的使用 细节
  5. 爬虫python下载电影_python爬虫抓取电影天堂最新电影
  6. 记:《洛克菲勒留给儿子的38封信》-- 34
  7. Navicat 筛选或插入某个字段出现1060 - Duplicate column name ‘XXX‘错误,以及导入sql文件时数据丢失问题。
  8. windows2008R2新增磁盘处于脱机状态解决办法
  9. oracle报错00801,PLS-00801: Type xxxxxx has no MAP method 这到底是啥啊……
  10. 希捷 混合硬盘 装linux,Seagate SSHD混合硬盘初始化和系统安装