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

[java] view plaincopy
  1. @UiccCard.java
  2. public void update(Context c, CommandsInterface ci, IccCardStatus ics) {
  3. synchronized (mLock) {
  4. if (mUiccApplications.length > 0 && mUiccApplications[0] != null) {
  5. //创建CatService
  6. mCatService = CatService.getInstance(mCi, mContext, this);
  7. } else {
  8. if (mCatService != null) {
  9. mCatService.dispose();
  10. }
  11. mCatService = null;
  12. }
  13. }
  14. }

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

[java] view plaincopy
  1. @CatService.java
  2. public static CatService getInstance(CommandsInterface ci, Context context, UiccCard ic) {
  3. UiccCardApplication ca = null;
  4. IccFileHandler fh = null;
  5. IccRecords ir = null;
  6. if (ic != null) {
  7. //获取UiccCardApplication、IccFileHandler、IccRecords等对象
  8. ca = ic.getApplicationIndex(0);
  9. if (ca != null) {
  10. fh = ca.getIccFileHandler();
  11. ir = ca.getIccRecords();
  12. }
  13. }
  14. synchronized (sInstanceLock) {
  15. if (sInstance == null) {
  16. if (ci == null || ca == null || ir == null || context == null || fh == null || ic == null) {
  17. return null;
  18. }
  19. //创建CatService的消息处理线程
  20. HandlerThread thread = new HandlerThread("Cat Telephony service");
  21. thread.start();
  22. //创建CatService实例对象
  23. sInstance = new CatService(ci, ca, ir, context, fh, ic);
  24. } else if ((ir != null) && (mIccRecords != ir)) {
  25. //CatService已经被创建过,只需要更新其监听器
  26. if (mIccRecords != null) {
  27. mIccRecords.unregisterForRecordsLoaded(sInstance);
  28. }
  29. if (mUiccApplication != null) {
  30. mUiccApplication.unregisterForReady(sInstance);
  31. }
  32. mIccRecords = ir;
  33. mUiccApplication = ca;
  34. mIccRecords.registerForRecordsLoaded(sInstance, MSG_ID_ICC_RECORDS_LOADED, null);
  35. mUiccApplication.registerForReady(sInstance, MSG_ID_SIM_READY, null);
  36. } else {
  37. CatLog.d(sInstance, "Return current sInstance");
  38. }
  39. return sInstance;
  40. }
  41. }

看他的构造函数:

[java] view plaincopy
  1. private CatService(CommandsInterface ci, UiccCardApplication ca, IccRecords ir, Context context, IccFileHandler fh, UiccCard ic) {
  2. if (ci == null || ca == null || ir == null || context == null || fh == null || ic == null) {
  3. throw new NullPointerException( "Service: Input parameters must not be null");
  4. }
  5. mCmdIf = ci;
  6. mContext = context;
  7. //获取RilMessageDecoder对象
  8. mMsgDecoder = RilMessageDecoder.getInstance(this, fh);
  9. //向RIL注册监听器
  10. mCmdIf.setOnCatSessionEnd(this, MSG_ID_SESSION_END, null);
  11. mCmdIf.setOnCatProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null);
  12. mCmdIf.setOnCatEvent(this, MSG_ID_EVENT_NOTIFY, null);
  13. mCmdIf.setOnCatCallSetUp(this, MSG_ID_CALL_SETUP, null);
  14. mIccRecords = ir;
  15. mUiccApplication = ca;
  16. //向UiccCardApplication对象注册SIM Ready的监听器
  17. //向IccRecords对象注册Icc Record Loaded的监听器
  18. mUiccApplication.registerForReady(this, MSG_ID_SIM_READY, null);
  19. mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null);
  20. mStkAppInstalled = isStkAppInstalled();
  21. }

我们看到在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已经运行,准备接受消息。下面来看详细流程:

[java] view plaincopy
  1. public void handleMessage(Message msg) {
  2. switch (msg.what) {
  3. case MSG_ID_SIM_READY:
  4. //向RIL发送消息
  5. mCmdIf.reportStkServiceIsRunning(null);
  6. break;
  7. default:
  8. throw new AssertionError("Unrecognized CAT command: " + msg.what);
  9. }
  10. }

此时的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业务,将接收不到该消息

[java] view plaincopy
  1. public void handleMessage(Message msg) {
  2. switch (msg.what) {
  3. case MSG_ID_SESSION_END:
  4. case MSG_ID_PROACTIVE_COMMAND:
  5. case MSG_ID_EVENT_NOTIFY:
  6. case MSG_ID_REFRESH:
  7. String data = null;
  8. if (msg.obj != null) {
  9. AsyncResult ar = (AsyncResult) msg.obj;
  10. if (ar != null && ar.result != null) {
  11. try {
  12. data = (String) ar.result;
  13. } catch (ClassCastException e) {
  14. break;
  15. }
  16. }
  17. }
  18. //通过RilMessageDecoder去解析数据
  19. mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data));
  20. break;
  21. default:
  22. throw new AssertionError("Unrecognized CAT command: " + msg.what);
  23. }
  24. }

我们看到,对于拿到的数据时通过mMsgDecoder去解析的,也就是RilMessageDecoder的对象,这个对象是在CatService的构造函数中被创建的。我们来看具体的解析数据的过程:

[java] view plaincopy
  1. @RilMessageDecoder.java
  2. public void sendStartDecodingMessageParams(RilMessage rilMsg) {
  3. Message msg = obtainMessage(CMD_START);
  4. msg.obj = rilMsg;
  5. sendMessage(msg);
  6. }

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

[java] view plaincopy
  1. private class StateStart extends State {
  2. @Override
  3. public boolean processMessage(Message msg) {
  4. if (msg.what == CMD_START) {
  5. if (decodeMessageParams((RilMessage)msg.obj)) {
  6. transitionTo(mStateCmdParamsReady);
  7. }
  8. } else {
  9. CatLog.d(this, "StateStart unexpected expecting START=" + CMD_START + " got " + msg.what);
  10. }
  11. return true;
  12. }
  13. }

然后在decodeMessageParams()方法中被解析:

[java] view plaincopy
  1. private boolean decodeMessageParams(RilMessage rilMsg) {
  2. boolean decodingStarted;
  3. mCurrentRilMessage = rilMsg;
  4. switch(rilMsg.mId) {
  5. case CatService.MSG_ID_PROACTIVE_COMMAND:
  6. case CatService.MSG_ID_EVENT_NOTIFY:
  7. case CatService.MSG_ID_REFRESH:
  8. byte[] rawData = null;
  9. try {
  10. rawData = IccUtils.hexStringToBytes((String) rilMsg.mData);
  11. } catch (Exception e) {
  12. }
  13. try {
  14. // Start asynch parsing of the command parameters.
  15. mCmdParamsFactory.make(BerTlv.decode(rawData));
  16. decodingStarted = true;
  17. } catch (ResultException e) {
  18. }
  19. break;
  20. default:
  21. decodingStarted = false;
  22. break;
  23. }
  24. return decodingStarted;
  25. }

在decodeMessageParams()中先通过hexStringToBytes()方法将得到的Modem数据转换为byte类型的数组数据rawData,然后将此数据交给CommandParamsFactory类的make()方法去解析,经过make的解析就可以得到每项STK菜单的图标、菜单文本以及需要的控件等信息,其解析过程为:

[java] view plaincopy
  1. @CommandParamsFactory.java
  2. void make(BerTlv berTlv) {
  3. mCmdParams = null;
  4. mIconLoadState = LOAD_NO_ICON;
  5. boolean cmdPending = false;
  6. List<ComprehensionTlv> ctlvs = berTlv.getComprehensionTlvs();
  7. CommandDetails cmdDet = processCommandDetails(ctlvs);
  8. //得到当前的命令类型
  9. AppInterface.CommandType cmdType = AppInterface.CommandType .fromInt(cmdDet.typeOfCommand);
  10. try {
  11. switch (cmdType) {
  12. case SET_UP_MENU:
  13. cmdPending = processSelectItem(cmdDet, ctlvs);
  14. break;
  15. case SELECT_ITEM:
  16. cmdPending = processSelectItem(cmdDet, ctlvs);
  17. break;
  18. case DISPLAY_TEXT:
  19. cmdPending = processDisplayText(cmdDet, ctlvs);
  20. break;
  21. case SET_UP_IDLE_MODE_TEXT:
  22. cmdPending = processSetUpIdleModeText(cmdDet, ctlvs);
  23. break;
  24. case GET_INKEY:
  25. cmdPending = processGetInkey(cmdDet, ctlvs);
  26. break;
  27. case GET_INPUT:
  28. cmdPending = processGetInput(cmdDet, ctlvs);
  29. break;
  30. case SEND_DTMF:
  31. case SEND_SMS:
  32. case SEND_SS:
  33. case SEND_USSD:
  34. cmdPending = processEventNotify(cmdDet, ctlvs);
  35. break;
  36. case GET_CHANNEL_STATUS:
  37. case SET_UP_CALL:
  38. cmdPending = processSetupCall(cmdDet, ctlvs);
  39. break;
  40. case REFRESH:
  41. processRefresh(cmdDet, ctlvs);
  42. cmdPending = false;
  43. break;
  44. case LAUNCH_BROWSER:
  45. cmdPending = processLaunchBrowser(cmdDet, ctlvs);
  46. break;
  47. case PLAY_TONE:
  48. cmdPending = processPlayTone(cmdDet, ctlvs);
  49. break;
  50. case PROVIDE_LOCAL_INFORMATION:
  51. cmdPending = processProvideLocalInfo(cmdDet, ctlvs);
  52. break;
  53. case OPEN_CHANNEL:
  54. case CLOSE_CHANNEL:
  55. case RECEIVE_DATA:
  56. case SEND_DATA:
  57. cmdPending = processBIPClient(cmdDet, ctlvs);
  58. break;
  59. default:
  60. // unsupported proactive commands
  61. mCmdParams = new CommandParams(cmdDet);
  62. sendCmdParams(ResultCode.BEYOND_TERMINAL_CAPABILITY);
  63. return;
  64. }
  65. } catch (ResultException e) {
  66. }
  67. if (!cmdPending) {
  68. sendCmdParams(ResultCode.OK);
  69. }
  70. }

从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()中发生的:

[java] view plaincopy
  1. private boolean processSelectItem(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs) throws ResultException {
  2. Menu menu = new Menu();
  3. IconId titleIconId = null;
  4. ItemsIconId itemsIconId = null;
  5. Iterator<ComprehensionTlv> iter = ctlvs.iterator();
  6. //搜索并解析该STK应用的名字,比如“神州行天地”
  7. ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
  8. if (ctlv != null) {
  9. menu.title = ValueParser.retrieveAlphaId(ctlv);
  10. }
  11. //循环解析该STK应用的所有根目录菜单
  12. //比如对于神州行的卡,就会有“轻松问候”、“短信群发”、“优惠快讯”、“业务精选”等菜单
  13. while (true) {
  14. ctlv = searchForNextTag(ComprehensionTlvTag.ITEM, iter);
  15. if (ctlv != null) {
  16. menu.items.add(ValueParser.retrieveItem(ctlv));
  17. } else {
  18. break;
  19. }
  20. }
  21. if (menu.items.size() == 0) {
  22. throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
  23. }
  24. //搜索ITEM_ID
  25. ctlv = searchForTag(ComprehensionTlvTag.ITEM_ID, ctlvs);
  26. if (ctlv != null) {
  27. menu.defaultItem = ValueParser.retrieveItemId(ctlv) - 1;
  28. }
  29. //搜索ICON_ID
  30. ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
  31. if (ctlv != null) {
  32. mIconLoadState = LOAD_SINGLE_ICON;
  33. titleIconId = ValueParser.retrieveIconId(ctlv);
  34. menu.titleIconSelfExplanatory = titleIconId.selfExplanatory;
  35. }
  36. ctlv = searchForTag(ComprehensionTlvTag.ITEM_ICON_ID_LIST, ctlvs);
  37. if (ctlv != null) {
  38. mIconLoadState = LOAD_MULTI_ICONS;
  39. itemsIconId = ValueParser.retrieveItemsIconId(ctlv);
  40. menu.itemsIconSelfExplanatory = itemsIconId.selfExplanatory;
  41. }
  42. boolean presentTypeSpecified = (cmdDet.commandQualifier & 0x01) != 0;
  43. if (presentTypeSpecified) {
  44. if ((cmdDet.commandQualifier & 0x02) == 0) {
  45. menu.presentationType = PresentationType.DATA_VALUES;
  46. } else {
  47. menu.presentationType = PresentationType.NAVIGATION_OPTIONS;
  48. }
  49. }
  50. menu.softKeyPreferred = (cmdDet.commandQualifier & 0x04) != 0;
  51. menu.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
  52. //用解析的菜单项构建mCmdParams对象
  53. mCmdParams = new SelectItemParams(cmdDet, menu, titleIconId != null);
  54. //处理图标的载入
  55. switch(mIconLoadState) {
  56. case LOAD_NO_ICON:
  57. return false;
  58. case LOAD_SINGLE_ICON:
  59. mIconLoader.loadIcon(titleIconId.recordNumber, this .obtainMessage(MSG_ID_LOAD_ICON_DONE));
  60. break;
  61. case LOAD_MULTI_ICONS:
  62. int[] recordNumbers = itemsIconId.recordNumbers;
  63. if (titleIconId != null) {
  64. // Create a new array for all the icons (title and items).
  65. recordNumbers = new int[itemsIconId.recordNumbers.length + 1];
  66. recordNumbers[0] = titleIconId.recordNumber;
  67. System.arraycopy(itemsIconId.recordNumbers, 0, recordNumbers, 1, itemsIconId.recordNumbers.length);
  68. }
  69. mIconLoader.loadIcons(recordNumbers, this .obtainMessage(MSG_ID_LOAD_ICON_DONE));
  70. break;
  71. }
  72. return true;
  73. }

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

2.2.2、SELECT_ITEM

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

2.2.3、DISPLAY_TEXT

DISPLAY_TEXT的作用主要是显示一个提示框,包括开机后的运营商欢迎提醒。

[java] view plaincopy
  1. private boolean processDisplayText(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs) throws ResultException {
  2. TextMessage textMsg = new TextMessage();
  3. IconId iconId = null;
  4. //得到显示的文本,比如开机后的运营商欢迎语
  5. ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING, ctlvs);
  6. if (ctlv != null) {
  7. textMsg.text = ValueParser.retrieveTextString(ctlv);
  8. }
  9. if (textMsg.text == null) {
  10. throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
  11. }
  12. ctlv = searchForTag(ComprehensionTlvTag.IMMEDIATE_RESPONSE, ctlvs);
  13. if (ctlv != null) {
  14. textMsg.responseNeeded = false;
  15. }
  16. //图标
  17. ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
  18. if (ctlv != null) {
  19. iconId = ValueParser.retrieveIconId(ctlv);
  20. textMsg.iconSelfExplanatory = iconId.selfExplanatory;
  21. }
  22. //显示的持续时间
  23. ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
  24. if (ctlv != null) {
  25. textMsg.duration = ValueParser.retrieveDuration(ctlv);
  26. }
  27. textMsg.isHighPriority = (cmdDet.commandQualifier & 0x01) != 0;
  28. textMsg.userClear = (cmdDet.commandQualifier & 0x80) != 0;
  29. //构建mCmdParams
  30. mCmdParams = new DisplayTextParams(cmdDet, textMsg);
  31. if (iconId != null) {
  32. mIconLoadState = LOAD_SINGLE_ICON;
  33. mIconLoader.loadIcon(iconId.recordNumber, this .obtainMessage(MSG_ID_LOAD_ICON_DONE));
  34. return true;
  35. }
  36. return false;
  37. }

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

2.2.4、GET_INPUT

当某个STK菜单需要弹出编辑框时,比如“群发短信”的功能,就会通过processGetInput()构建编辑框。

[java] view plaincopy
  1. private boolean processGetInput(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs) throws ResultException {
  2. Input input = new Input();
  3. IconId iconId = null;
  4. //要显示的提示字串,比如“输入内容”
  5. ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.TEXT_STRING, ctlvs);
  6. if (ctlv != null) {
  7. input.text = ValueParser.retrieveTextString(ctlv);
  8. } else {
  9. throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
  10. }
  11. //得到输入框的输入最大长度
  12. ctlv = searchForTag(ComprehensionTlvTag.RESPONSE_LENGTH, ctlvs);
  13. if (ctlv != null) {
  14. try {
  15. byte[] rawValue = ctlv.getRawValue();
  16. int valueIndex = ctlv.getValueIndex();
  17. input.minLen = rawValue[valueIndex] & 0xff;
  18. input.maxLen = rawValue[valueIndex + 1] & 0xff;
  19. } catch (IndexOutOfBoundsException e) {
  20. throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
  21. }
  22. } else {
  23. throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);
  24. }
  25. ctlv = searchForTag(ComprehensionTlvTag.DEFAULT_TEXT, ctlvs);
  26. if (ctlv != null) {
  27. input.defaultText = ValueParser.retrieveTextString(ctlv);
  28. }
  29. // parse icon identifier
  30. ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
  31. if (ctlv != null) {
  32. iconId = ValueParser.retrieveIconId(ctlv);
  33. }
  34. input.digitOnly = (cmdDet.commandQualifier & 0x01) == 0;
  35. input.ucs2 = (cmdDet.commandQualifier & 0x02) != 0;
  36. input.echo = (cmdDet.commandQualifier & 0x04) == 0;
  37. input.packed = (cmdDet.commandQualifier & 0x08) != 0;
  38. input.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;
  39. mCmdParams = new GetInputParams(cmdDet, input);
  40. if (iconId != null) {
  41. mIconLoadState = LOAD_SINGLE_ICON;
  42. mIconLoader.loadIcon(iconId.recordNumber, this .obtainMessage(MSG_ID_LOAD_ICON_DONE));
  43. return true;
  44. }
  45. return false;
  46. }

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

2.2.5、SEND_SMS

当通过STK向运营商或者其他用户发送短信时, RIL就会向CatService发送SEND_SMS的消息,此时CatService要做的就是通知StkAppService去显示一个Toast框提示“正在发送信息”(而不是去发送信息)

[java] view plaincopy
  1. private boolean processEventNotify(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs) throws ResultException {
  2. TextMessage textMsg = new TextMessage();
  3. IconId iconId = null;
  4. //发送短信时的提示语,比如“正在发送消息…”
  5. ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
  6. textMsg.text = ValueParser.retrieveAlphaId(ctlv);
  7. ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
  8. if (ctlv != null) {
  9. iconId = ValueParser.retrieveIconId(ctlv);
  10. textMsg.iconSelfExplanatory = iconId.selfExplanatory;
  11. }
  12. textMsg.responseNeeded = false;
  13. mCmdParams = new DisplayTextParams(cmdDet, textMsg);
  14. if (iconId != null) {
  15. mIconLoadState = LOAD_SINGLE_ICON;
  16. mIconLoader.loadIcon(iconId.recordNumber, this .obtainMessage(MSG_ID_LOAD_ICON_DONE));
  17. return true;
  18. }
  19. return false;
  20. }

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

2.2.6、CommandParamsFactory作用

然后回到make()方法中,将解析的结果发送给RilMessageDecoder对象:

[java] view plaincopy
  1. void make(BerTlv berTlv) {
  2. try {
  3. switch (cmdType) {
  4. case SET_UP_MENU:
  5. case SELECT_ITEM:
  6. case DISPLAY_TEXT:
  7. case SET_UP_IDLE_MODE_TEXT:
  8. case GET_INKEY:
  9. case GET_INPUT:
  10. case SEND_DTMF:
  11. case SEND_SMS:
  12. case SEND_SS:
  13. case SEND_USSD:
  14. case GET_CHANNEL_STATUS:
  15. case SET_UP_CALL:
  16. case REFRESH:
  17. case LAUNCH_BROWSER:
  18. case PLAY_TONE:
  19. case PROVIDE_LOCAL_INFORMATION:
  20. case OPEN_CHANNEL:
  21. case CLOSE_CHANNEL:
  22. case RECEIVE_DATA:
  23. case SEND_DATA:
  24. default:
  25. }
  26. } catch (ResultException e) {
  27. }
  28. if (!cmdPending) {
  29. //将解析的mCmdParams发送给RilMessageDecoder
  30. sendCmdParams(ResultCode.OK);
  31. }
  32. }
  33. private void sendCmdParams(ResultCode resCode) {
  34. mCaller.sendMsgParamsDecoded(resCode, mCmdParams);
  35. }

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

[java] view plaincopy
  1. @RilMessageDecoder.java
  2. public void sendMsgParamsDecoded(ResultCode resCode, CommandParams cmdParams) {
  3. Message msg = obtainMessage(RilMessageDecoder.CMD_PARAMS_READY);
  4. msg.arg1 = resCode.value();
  5. msg.obj = cmdParams;
  6. sendMessage(msg);
  7. }

至此,CommandParamsFactory的任务已经完成, 其主要作用体现在:1、解析SIM卡上报的STK数据;2、将解析后的数据发送给RilMessageDecoder
        接下来就 需要RilMessageDecoder将解析的结果发送给CatService
        当RilMessageDecoder通过sendMsgParamsDecoded()将消息发送出去后,经过其父类StateMachine的处理,再次由RilMessageDecoder接收到消息:

[java] view plaincopy
  1. private class StateCmdParamsReady extends State {
  2. @Override
  3. public boolean processMessage(Message msg) {
  4. if (msg.what == CMD_PARAMS_READY) {
  5. //接收到消息
  6. mCurrentRilMessage.mResCode = ResultCode.fromInt(msg.arg1);
  7. mCurrentRilMessage.mData = msg.obj;
  8. sendCmdForExecution(mCurrentRilMessage);
  9. transitionTo(mStateStart);
  10. } else {
  11. CatLog.d(this, "StateCmdParamsReady expecting CMD_PARAMS_READY=" + CMD_PARAMS_READY + " got " + msg.what);
  12. deferMessage(msg);
  13. }
  14. return true;
  15. }
  16. }

然后通过sendCmdForExecution()把解析结果重新发送给CatService:

[java] view plaincopy
  1. private void sendCmdForExecution(RilMessage rilMsg) {
  2. Message msg = mCaller.obtainMessage(CatService.MSG_ID_RIL_MSG_DECODED,
  3. new RilMessage(rilMsg));
  4. msg.sendToTarget();
  5. }

然后在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。
        下面来看该消息的处理流程:

[java] view plaincopy
  1. @CatService.java
  2. public void handleMessage(Message msg) {
  3. switch (msg.what) {
  4. case MSG_ID_RIL_MSG_DECODED:
  5. handleRilMsg((RilMessage) msg.obj);
  6. break;
  7. default:
  8. throw new AssertionError("Unrecognized CAT command: " + msg.what);
  9. }
  10. }

接着往下看:

[java] view plaincopy
  1. private void handleRilMsg(RilMessage rilMsg) {
  2. CommandParams cmdParams = null;
  3. switch (rilMsg.mId) {
  4. case MSG_ID_PROACTIVE_COMMAND:
  5. try {
  6. //拿到解析后的数据
  7. cmdParams = (CommandParams) rilMsg.mData;
  8. } catch (ClassCastException e) {
  9. }
  10. if (cmdParams != null) {
  11. if (rilMsg.mResCode == ResultCode.OK) {
  12. //处理当前的数据
  13. handleCommand(cmdParams, true);
  14. } else {
  15. sendTerminalResponse(cmdParams.mCmdDet, rilMsg.mResCode, false, 0, null);
  16. }
  17. }
  18. break;
  19. }
  20. }
  21. private void handleCommand(CommandParams cmdParams, boolean isProactiveCmd) {
  22. CharSequence message;
  23. CatCmdMessage cmdMsg = new CatCmdMessage(cmdParams);
  24. switch (cmdParams.getCommandType()) {
  25. case SET_UP_MENU:
  26. if (removeMenu(cmdMsg.getMenu())) {
  27. mMenuCmd = null;
  28. } else {
  29. mMenuCmd = cmdMsg;
  30. }
  31. sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null);
  32. break;
  33. case DISPLAY_TEXT:
  34. if (!cmdMsg.geTextMessage().responseNeeded) {
  35. sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false, 0, null);
  36. }
  37. break;
  38. case SELECT_ITEM:
  39. case GET_INPUT:
  40. case GET_INKEY:
  41. break;
  42. case SEND_DTMF:
  43. case SEND_SMS:
  44. case SEND_SS:
  45. case SEND_USSD:
  46. if ((((DisplayTextParams)cmdParams).mTextMsg.text != null) && (((DisplayTextParams)cmdParams).mTextMsg.text.equals(STK_DEFAULT))) {
  47. message = mContext.getText(com.android.internal.R.string.sending);
  48. ((DisplayTextParams)cmdParams).mTextMsg.text = message.toString();
  49. }
  50. break;
  51. case PLAY_TONE:
  52. break;
  53. case SET_UP_CALL:
  54. if ((((CallSetupParams) cmdParams).mConfirmMsg.text != null) && (((CallSetupParams) cmdParams).mConfirmMsg.text.equals(STK_DEFAULT))) {
  55. message = mContext.getText(com.android.internal.R.string.SetupCallDefault);
  56. ((CallSetupParams) cmdParams).mConfirmMsg.text = message.toString();
  57. }
  58. break;
  59. default:
  60. CatLog.d(this, "Unsupported command");
  61. return;
  62. }
  63. mCurrntCmd = cmdMsg;
  64. Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
  65. intent.putExtra("STK CMD", cmdMsg);
  66. mContext.sendBroadcast(intent);
  67. }

我们看到,在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的消息流程类似,只是更加的简单:

[java] view plaincopy
  1. @CatService.java
  2. public void handleMessage(Message msg) {
  3. switch (msg.what) {
  4. case MSG_ID_SESSION_END:
  5. case MSG_ID_PROACTIVE_COMMAND:
  6. case MSG_ID_EVENT_NOTIFY:
  7. case MSG_ID_REFRESH:
  8. CatLog.d(this, "ril message arrived");
  9. String data = null;
  10. if (msg.obj != null) {
  11. AsyncResult ar = (AsyncResult) msg.obj;
  12. if (ar != null && ar.result != null) {
  13. try {
  14. data = (String) ar.result;
  15. } catch (ClassCastException e) {
  16. break;
  17. }
  18. }
  19. }
  20. //让RilMessageDecoder去处理
  21. mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data));
  22. break;
  23. }
  24. }

然后就交给RilMessageDecoder中的decodeMessageParams()处理:

[java] view plaincopy
  1. private boolean decodeMessageParams(RilMessage rilMsg) {
  2. boolean decodingStarted;
  3. mCurrentRilMessage = rilMsg;
  4. switch(rilMsg.mId) {
  5. case CatService.MSG_ID_SESSION_END:
  6. case CatService.MSG_ID_CALL_SETUP:
  7. mCurrentRilMessage.mResCode = ResultCode.OK;
  8. sendCmdForExecution(mCurrentRilMessage);
  9. decodingStarted = false;
  10. break;
  11. default:
  12. decodingStarted = false;
  13. break;
  14. }
  15. return decodingStarted;
  16. }

这里和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出来:

[java] view plaincopy
  1. Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
  2. intent.putExtra("STK CMD", cmdMsg);
  3. mContext.sendBroadcast(intent);

这里发送的Intent,就会被STK模块的StkCmdReceiver接收到,而且该Intent中的"STK CMD"中存放的就是当前的解析结果。

[java] view plaincopy
  1. @StkCmdReceiver.java
  2. public void onReceive(Context context, Intent intent) {
  3. String action = intent.getAction();
  4. if (action.equals(AppInterface.CAT_CMD_ACTION)) {
  5. handleCommandMessage(context, intent);
  6. } else if (action.equals(AppInterface.CAT_SESSION_END_ACTION)) {
  7. handleSessionEnd(context, intent);
  8. }
  9. }

然后就会在handleCommandMessage()中启动StkAppService:

[java] view plaincopy
  1. private void handleCommandMessage(Context context, Intent intent) {
  2. Bundle args = new Bundle();
  3. args.putInt(StkAppService.OPCODE, StkAppService.OP_CMD);
  4. args.putParcelable(StkAppService.CMD_MSG, intent .getParcelableExtra("STK CMD"));
  5. context.startService(new Intent(context, StkAppService.class) .putExtras(args));
  6. }

请注意,在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:

[java] view plaincopy
  1. @StkAppService.java
  2. public void onStart(Intent intent, int startId) {
  3. mStkService = com.android.internal.telephony.cat.CatService .getInstance();
  4. if (mStkService == null) {
  5. }
  6. Bundle args = intent.getExtras();
  7. if (args == null) {
  8. return;
  9. }
  10. Message msg = mServiceHandler.obtainMessage();
  11. msg.arg1 = args.getInt(OPCODE);
  12. switch(msg.arg1) {
  13. case OP_CMD:
  14. msg.obj = args.getParcelable(CMD_MSG);
  15. break;
  16. case OP_RESPONSE:
  17. msg.obj = args;
  18. case OP_LAUNCH_APP:
  19. case OP_END_SESSION:
  20. case OP_BOOT_COMPLETED:
  21. break;
  22. default:
  23. return;
  24. }
  25. mServiceHandler.sendMessage(msg);
  26. }

然后在ServiceHandler中处理:

[java] view plaincopy
  1. private final class ServiceHandler extends Handler {
  2. @Override
  3. public void handleMessage(Message msg) {
  4. int opcode = msg.arg1;
  5. switch (opcode) {
  6. case OP_LAUNCH_APP:
  7. break;
  8. case OP_CMD:
  9. CatCmdMessage cmdMsg = (CatCmdMessage) msg.obj;
  10. if (!isCmdInteractive(cmdMsg)) {
  11. handleCmd(cmdMsg);
  12. } else {
  13. if (!mCmdInProgress) {
  14. mCmdInProgress = true;
  15. handleCmd((CatCmdMessage) msg.obj);
  16. } else {
  17. mCmdsQ.addLast(new DelayedCmd(OP_CMD, (CatCmdMessage) msg.obj));
  18. }
  19. }
  20. break;
  21. case OP_RESPONSE:
  22. break;
  23. case OP_END_SESSION:
  24. break;
  25. case OP_BOOT_COMPLETED:
  26. break;
  27. case OP_DELAYED_MSG:
  28. break;
  29. }
  30. }
  31. }

由于当前的opcode是OP_CMD,那么在该case中进行其他的处理,这里会根据isCmdInteractive()来判断当前的请求,不过对于SET_UP_MENU、DISPLAY_TEXT等消息,最终还是要通过handleCmd()去处理:

[java] view plaincopy
  1. private void handleCmd(CatCmdMessage cmdMsg) {
  2. if (cmdMsg == null) {
  3. return;
  4. }
  5. switch (cmdMsg.getCmdType()) {
  6. case DISPLAY_TEXT:
  7. //弹出提示框
  8. TextMessage msg = cmdMsg.geTextMessage();
  9. responseNeeded = msg.responseNeeded;
  10. waitForUsersResponse = msg.responseNeeded;
  11. if (lastSelectedItem != null) {
  12. msg.title = lastSelectedItem;
  13. } else if (mMainCmd != null){
  14. msg.title = mMainCmd.getMenu().title;
  15. } else {
  16. msg.title = "";
  17. }
  18. launchTextDialog();
  19. break;
  20. case SELECT_ITEM:
  21. //载入下一级菜单
  22. mCurrentMenu = cmdMsg.getMenu();
  23. launchMenuActivity(cmdMsg.getMenu());
  24. break;
  25. case SET_UP_MENU:
  26. //显示列表
  27. mMainCmd = mCurrentCmd;
  28. mCurrentMenu = cmdMsg.getMenu();
  29. if (removeMenu()) {
  30. CatLog.d(this, "Uninstall App");
  31. mCurrentMenu = null;
  32. StkAppInstaller.unInstall(mContext);
  33. } else {
  34. CatLog.d(this, "Install App");
  35. StkAppInstaller.install(mContext);
  36. }
  37. if (mMenuIsVisibile) {
  38. launchMenuActivity(null);
  39. }
  40. break;
  41. case GET_INPUT:
  42. case GET_INKEY:
  43. //显示输入框
  44. launchInputActivity();
  45. break;
  46. case SET_UP_IDLE_MODE_TEXT:
  47. waitForUsersResponse = false;
  48. launchIdleText();
  49. break;
  50. case SEND_DTMF:
  51. case SEND_SMS:
  52. case SEND_SS:
  53. case SEND_USSD:
  54. //发送信息的提示
  55. waitForUsersResponse = false;
  56. launchEventMessage();
  57. break;
  58. case LAUNCH_BROWSER:
  59. //载入浏览器
  60. launchConfirmationDialog(mCurrentCmd.geTextMessage());
  61. break;
  62. case SET_UP_CALL:
  63. //呼叫
  64. launchConfirmationDialog(mCurrentCmd.getCallSettings().confirmMsg);
  65. break;
  66. case PLAY_TONE:
  67. //播放铃声
  68. launchToneDialog();
  69. break;
  70. case OPEN_CHANNEL:
  71. launchOpenChannelDialog();
  72. break;
  73. case CLOSE_CHANNEL:
  74. case RECEIVE_DATA:
  75. case SEND_DATA:
  76. TextMessage m = mCurrentCmd.geTextMessage();
  77. if ((m != null) && (m.text == null)) {
  78. switch(cmdMsg.getCmdType()) {
  79. case CLOSE_CHANNEL:
  80. m.text = getResources().getString(R.string.default_close_channel_msg);
  81. break;
  82. case RECEIVE_DATA:
  83. m.text = getResources().getString(R.string.default_receive_data_msg);
  84. break;
  85. case SEND_DATA:
  86. m.text = getResources().getString(R.string.default_send_data_msg);
  87. break;
  88. }
  89. }
  90. launchEventMessage();
  91. break;
  92. }
  93. if (!waitForUsersResponse) {
  94. if (mCmdsQ.size() != 0) {
  95. callDelayedMsg();
  96. } else {
  97. mCmdInProgress = false;
  98. }
  99. }
  100. }

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

Uicc之CatService相关推荐

  1. Uicc之CatService(原)

    CatService主要负责STK菜单的相关事宜,本节我们就来分析该对象. 一.CatService的创建过程 在前面第二节中我们分析过,在UiccCard的更新过程中,会初始化CatService对 ...

  2. 嵌入式设备开发专题《NB-IoT开发,解读SIM卡、USIM卡、UICC卡、eSIM卡的区别》

    前言 1)不少客户拿到NB-IoT模组,才发现自己忘记买相应的SIM卡,无法进行相应功能开发 2)不知道买哪种SIM卡,买的SIM卡不适用,或者运营商不匹配,国内基本也就移动入库/电信入库/联通入库 ...

  3. UICC,USIM卡与SIM的区别

    USIM卡与SIM的区别 SIM卡(Subscriber Identity Module) ,即用户识别卡,是全球通数字移动电话的一张个人资料卡.它采用A 级加密方法制作,存储着用户的数据.鉴权方法及 ...

  4. 解读SIM卡、USIM卡、UICC卡、eSIM卡的区别

    SIM的英文全称是"Subscriber Identity Module",即"用户身份模块".它的主要作用是在移动终端设备与网络通讯时提供身份识别信息及存储数 ...

  5. SIM卡、USIM卡、UICC卡、eSIM卡的区别

    SIM的英文全称是"Subscriber Identity Module",即"用户身份模块".它的主要作用是在移动终端设备与网络通讯时提供身份识别信息及存储数 ...

  6. UICC 之 USIM 详解全系列——UICC基础知识介绍

    本人就职于国际知名终端厂商,负责modem芯片研发. 在5G早期负责终端数据业务层.核心网相关的开发工作,目前牵头6G算力网络技术标准研究. UICC 之 USIM 详解全系列--UICC(TS102 ...

  7. UICC 之 USIM 详解全系列——USIM App文件结构

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

  8. UICC 之 USIM 详解全系列

    本人就职于国际知名终端厂商,负责modem芯片研发. 在5G早期负责终端数据业务层.核心网相关的开发工作,目前牵头6G算力网络技术标准研究. 一.项目背景介绍与系列介绍 1.1 项目背景 1.2 US ...

  9. UICC 之 USIM 详解全系列——USIM Demo详解

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

最新文章

  1. ps作业素材和成品_看完俄罗斯这位PS大神的P图!分分钟感觉自己被秒杀!
  2. 你能用上降噪耳机,竟要感谢傅立叶变换?
  3. 解决类似umount target is busy挂载盘卸载不掉问题
  4. 谈谈基于机器学习的编程到底比传统编程强在哪里?
  5. html3D效果可以在手机打开吗,手机怎么打开HTML
  6. WinCE5.0 SMDK2410 BSP在GEC2410开发板上的移植(11)-BINFS在Nand上的实现(Multi-Bin的实现)
  7. java http 1.1_java11新特性HttpClient
  8. 【飞秋】网络的基础-域名篇
  9. c# const与readonly 关键字的比较
  10. 企业Linux安全机制遭遇信任危机 SELinux成骇客帮凶?
  11. 小白如何购买阿里云服务器(2019最详细教程)
  12. java8之StringJoiner。终于有像guava类库里的功能了
  13. mysql数据库rpm包安装_Linux rpm包安装MySQL数据库问题总结
  14. 浅谈Zookeeper客户端库Curator实现加锁的原理
  15. 世界robocode机器人的四大运动方式分析
  16. android stub,Android:ViewStub
  17. 永磁同步电机模型之坐标变换
  18. JetBrain Rider使用技巧(持续更新)
  19. consul剔除无效服务
  20. Android下载保存gif图片

热门文章

  1. 不走寻常路,一个程序媛十三年的野蛮生长
  2. 野蛮生长-HW上机1
  3. Python实现性能测试(locust)
  4. 【转】想做「互联网金融」产品经理,如何开始学习?
  5. 内部类都有哪些?什么是内部类
  6. 爬虫概念与编程学习之如何爬取视频网站页面(用HttpClient)(二)
  7. 完整的京东联盟自定义推广链接生产程序
  8. solidity采坑日记之智能合约返回事件内容解析
  9. BIM轻量化和Web的应用
  10. 计算出生日期至今的天数