Uicc之CatService

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


一、CatService的创建过程


        在前面第二节中我们分析过,在UiccCard的更新过程中,会初始化CatService对象:
  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. }  
        然后看具体的初始化流程:
  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. }  
        看他的构造函数:
  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.   
  8.     //获取RilMessageDecoder对象  
  9.     mMsgDecoder = RilMessageDecoder.getInstance(this, fh);  
  10.     //向RIL注册监听器  
  11.     mCmdIf.setOnCatSessionEnd(this, MSG_ID_SESSION_END, null);  
  12.     mCmdIf.setOnCatProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null);  
  13.     mCmdIf.setOnCatEvent(this, MSG_ID_EVENT_NOTIFY, null);  
  14.     mCmdIf.setOnCatCallSetUp(this, MSG_ID_CALL_SETUP, null);  
  15.   
  16.     mIccRecords = ir;  
  17.     mUiccApplication = ca;  
  18.     //向UiccCardApplication对象注册SIM Ready的监听器  
  19.     //向IccRecords对象注册Icc Record Loaded的监听器  
  20.     mUiccApplication.registerForReady(this, MSG_ID_SIM_READY, null);  
  21.     mIccRecords.registerForRecordsLoaded(this, MSG_ID_ICC_RECORDS_LOADED, null);  
  22.   
  23.     mStkAppInstalled = isStkAppInstalled();  
  24. }  
        我们看到在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已经运行,准备接受消息。下面来看详细流程:
  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业务,将接收不到该消息
  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的构造函数中被创建的。我们来看具体的解析数据的过程:
  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中被处理:
  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()方法中被解析:
  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菜单的图标、菜单文本以及需要的控件等信息,其解析过程为:
  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.     //得到当前的命令类型  
  10.     AppInterface.CommandType cmdType = AppInterface.CommandType .fromInt(cmdDet.typeOfCommand);  
  11.     try {  
  12.         switch (cmdType) {  
  13.             case SET_UP_MENU:  
  14.                 cmdPending = processSelectItem(cmdDet, ctlvs);  
  15.                 break;  
  16.             case SELECT_ITEM:  
  17.                 cmdPending = processSelectItem(cmdDet, ctlvs);  
  18.                 break;  
  19.             case DISPLAY_TEXT:  
  20.                 cmdPending = processDisplayText(cmdDet, ctlvs);  
  21.                 break;  
  22.             case SET_UP_IDLE_MODE_TEXT:  
  23.                 cmdPending = processSetUpIdleModeText(cmdDet, ctlvs);  
  24.                 break;  
  25.             case GET_INKEY:  
  26.                 cmdPending = processGetInkey(cmdDet, ctlvs);  
  27.                 break;  
  28.             case GET_INPUT:  
  29.                 cmdPending = processGetInput(cmdDet, ctlvs);  
  30.                 break;  
  31.             case SEND_DTMF:  
  32.             case SEND_SMS:  
  33.             case SEND_SS:  
  34.             case SEND_USSD:  
  35.                 cmdPending = processEventNotify(cmdDet, ctlvs);  
  36.                 break;  
  37.             case GET_CHANNEL_STATUS:  
  38.             case SET_UP_CALL:  
  39.                 cmdPending = processSetupCall(cmdDet, ctlvs);  
  40.                 break;  
  41.             case REFRESH:  
  42.                 processRefresh(cmdDet, ctlvs);  
  43.                 cmdPending = false;  
  44.                 break;  
  45.             case LAUNCH_BROWSER:  
  46.                 cmdPending = processLaunchBrowser(cmdDet, ctlvs);  
  47.                 break;  
  48.             case PLAY_TONE:  
  49.                 cmdPending = processPlayTone(cmdDet, ctlvs);  
  50.                 break;  
  51.             case PROVIDE_LOCAL_INFORMATION:  
  52.                 cmdPending = processProvideLocalInfo(cmdDet, ctlvs);  
  53.                 break;  
  54.             case OPEN_CHANNEL:  
  55.             case CLOSE_CHANNEL:  
  56.             case RECEIVE_DATA:  
  57.             case SEND_DATA:  
  58.                 cmdPending = processBIPClient(cmdDet, ctlvs);  
  59.                 break;  
  60.             default:  
  61.                 // unsupported proactive commands  
  62.                 mCmdParams = new CommandParams(cmdDet);  
  63.                 sendCmdParams(ResultCode.BEYOND_TERMINAL_CAPABILITY);  
  64.                 return;  
  65.         }  
  66.     } catch (ResultException e) {  
  67.     }  
  68.     if (!cmdPending) {  
  69.         sendCmdParams(ResultCode.OK);  
  70.     }  
  71. }  
        从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()中发生的:
  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.   
  7.     //搜索并解析该STK应用的名字,比如“神州行天地”  
  8.     ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);  
  9.     if (ctlv != null) {  
  10.         menu.title = ValueParser.retrieveAlphaId(ctlv);  
  11.     }  
  12.   
  13.     //循环解析该STK应用的所有根目录菜单  
  14.     //比如对于神州行的卡,就会有“轻松问候”、“短信群发”、“优惠快讯”、“业务精选”等菜单  
  15.     while (true) {  
  16.         ctlv = searchForNextTag(ComprehensionTlvTag.ITEM, iter);  
  17.         if (ctlv != null) {  
  18.             menu.items.add(ValueParser.retrieveItem(ctlv));  
  19.         } else {  
  20.             break;  
  21.         }  
  22.     }  
  23.   
  24.     if (menu.items.size() == 0) {  
  25.         throw new ResultException(ResultCode.REQUIRED_VALUES_MISSING);  
  26.     }  
  27.   
  28.     //搜索ITEM_ID  
  29.     ctlv = searchForTag(ComprehensionTlvTag.ITEM_ID, ctlvs);  
  30.     if (ctlv != null) {  
  31.         menu.defaultItem = ValueParser.retrieveItemId(ctlv) - 1;  
  32.     }  
  33.   
  34.     //搜索ICON_ID  
  35.     ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);  
  36.     if (ctlv != null) {  
  37.         mIconLoadState = LOAD_SINGLE_ICON;  
  38.         titleIconId = ValueParser.retrieveIconId(ctlv);  
  39.         menu.titleIconSelfExplanatory = titleIconId.selfExplanatory;  
  40.     }  
  41.   
  42.     ctlv = searchForTag(ComprehensionTlvTag.ITEM_ICON_ID_LIST, ctlvs);  
  43.     if (ctlv != null) {  
  44.         mIconLoadState = LOAD_MULTI_ICONS;  
  45.         itemsIconId = ValueParser.retrieveItemsIconId(ctlv);  
  46.         menu.itemsIconSelfExplanatory = itemsIconId.selfExplanatory;  
  47.     }  
  48.   
  49.     boolean presentTypeSpecified = (cmdDet.commandQualifier & 0x01) != 0;  
  50.     if (presentTypeSpecified) {  
  51.         if ((cmdDet.commandQualifier & 0x02) == 0) {  
  52.             menu.presentationType = PresentationType.DATA_VALUES;  
  53.         } else {  
  54.             menu.presentationType = PresentationType.NAVIGATION_OPTIONS;  
  55.         }  
  56.     }  
  57.     menu.softKeyPreferred = (cmdDet.commandQualifier & 0x04) != 0;  
  58.     menu.helpAvailable = (cmdDet.commandQualifier & 0x80) != 0;  
  59.   
  60.     //用解析的菜单项构建mCmdParams对象  
  61.     mCmdParams = new SelectItemParams(cmdDet, menu, titleIconId != null);  
  62.   
  63.     //处理图标的载入  
  64.     switch(mIconLoadState) {  
  65.         case LOAD_NO_ICON:  
  66.             return false;  
  67.         case LOAD_SINGLE_ICON:  
  68.             mIconLoader.loadIcon(titleIconId.recordNumber, this .obtainMessage(MSG_ID_LOAD_ICON_DONE));  
  69.             break;  
  70.         case LOAD_MULTI_ICONS:  
  71.             int[] recordNumbers = itemsIconId.recordNumbers;  
  72.             if (titleIconId != null) {  
  73.                 // Create a new array for all the icons (title and items).  
  74.                 recordNumbers = new int[itemsIconId.recordNumbers.length + 1];  
  75.                 recordNumbers[0] = titleIconId.recordNumber;  
  76.                 System.arraycopy(itemsIconId.recordNumbers, 0, recordNumbers, 1, itemsIconId.recordNumbers.length);  
  77.             }  
  78.             mIconLoader.loadIcons(recordNumbers, this .obtainMessage(MSG_ID_LOAD_ICON_DONE));  
  79.             break;  
  80.     }  
  81.     return true;  
  82. }  

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


2.2.2、SELECT_ITEM


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


2.2.3、DISPLAY_TEXT


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

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


2.2.4、GET_INPUT


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

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


2.2.5、SEND_SMS


        当通过STK向运营商或者其他用户发送短信时, RIL就会向CatService发送SEND_SMS的消息,此时CatService要做的就是通知StkAppService去显示一个Toast框提示“正在发送信息”(而不是去发送信息)
  1. private boolean processEventNotify(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs) throws ResultException {  
  2.     TextMessage textMsg = new TextMessage();  
  3.     IconId iconId = null;  
  4.   
  5.     //发送短信时的提示语,比如“正在发送消息…”  
  6.     ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);  
  7.     textMsg.text = ValueParser.retrieveAlphaId(ctlv);  
  8.   
  9.     ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);  
  10.     if (ctlv != null) {  
  11.         iconId = ValueParser.retrieveIconId(ctlv);  
  12.         textMsg.iconSelfExplanatory = iconId.selfExplanatory;  
  13.     }  
  14.   
  15.     textMsg.responseNeeded = false;  
  16.     mCmdParams = new DisplayTextParams(cmdDet, textMsg);  
  17.     if (iconId != null) {  
  18.         mIconLoadState = LOAD_SINGLE_ICON;  
  19.         mIconLoader.loadIcon(iconId.recordNumber, this .obtainMessage(MSG_ID_LOAD_ICON_DONE));  
  20.         return true;  
  21.     }  
  22.     return false;  
  23. }  

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


2.2.6、CommandParamsFactory作用


        然后回到make()方法中,将解析的结果发送给RilMessageDecoder对象:
  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:
  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接收到消息:
  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:
  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。
        下面来看该消息的处理流程:
  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. }  
        接着往下看:
  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, false0null);  
  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, false0null);  
  32.             break;  
  33.         case DISPLAY_TEXT:  
  34.             if (!cmdMsg.geTextMessage().responseNeeded) {  
  35.                 sendTerminalResponse(cmdParams.mCmdDet, ResultCode.OK, false0null);  
  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的消息流程类似,只是更加的简单:
  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()处理:
  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出来:

  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"中存放的就是当前的解析结果。
  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:
  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:
  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.   
  11.     Message msg = mServiceHandler.obtainMessage();  
  12.     msg.arg1 = args.getInt(OPCODE);  
  13.     switch(msg.arg1) {  
  14.         case OP_CMD:  
  15.             msg.obj = args.getParcelable(CMD_MSG);  
  16.             break;  
  17.         case OP_RESPONSE:  
  18.             msg.obj = args;  
  19.         case OP_LAUNCH_APP:  
  20.         case OP_END_SESSION:  
  21.         case OP_BOOT_COMPLETED:  
  22.             break;  
  23.         default:  
  24.             return;  
  25.     }  
  26.     mServiceHandler.sendMessage(msg);  
  27. }  
        然后在ServiceHandler中处理:
  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()去处理:
  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.   
  94.     if (!waitForUsersResponse) {  
  95.         if (mCmdsQ.size() != 0) {  
  96.             callDelayedMsg();  
  97.         } else {  
  98.             mCmdInProgress = false;  
  99.         }  
  100.     }  
  101. }  
        接下来,就会对当前的请求做UI层的处理,比如当需要弹出对话框时(DISPLAY_TEXT)就会通过launchTextDialog()方法弹出对话框,当需要弹出菜单时(SELECT_ITEM/SET_UP_MENU)就会通过launchMenuActivity()方法来实现,当需要一个编辑框(GET_INPUT)时,就会通过launchInputActivity()来实现。
        至此,Uicc相关的知识总结完毕。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值