一、Stk应用简介
安卓源码packages/apps/Stk目录
Stk是全称Sim Tool Kit,即用户识别应用发展工具。该应用提供了用户与SIM卡功能交互的接口。Stk应用里面展示的所有功能和用户进行的所有操作都是与SIM卡运营商进行交互的,因为要走网络流程,所以响应一般比较慢。
注意只有在SIM卡可用的情况下才可以使用STK,例如手机在飞行模式或者没有驻网的情况下,STK应用是不可用的。
二、STK应用架构图
做了一张架构图,如下:
图比较大,可以下载下来观看。
三、STK功能代码详解
Stk应用是Telephony模块的CatService与用户交互的桥梁,功能都集中在StkAppService.java类。它实现了两个流程:
- 响应CatService发来的指令
- 响应用户操作,发送消息给CatService
3.1 Stk运行流程简述
- 在开机后,由BootCompeleteReceiver.java接收开机广播,启动StkAppService,并携带
OP_BOOT_COMPLETED
参数。StkService中由ServiceHandler处理,根据OP_BOOT_COMPLETED参数,检查SIM卡情况,再确定是否启用Stk组件 - 用户进入Stk主界面,启动StkLauncherActivity,接着启动StkAppService,携带OP_LAUNCH_APP参数,在StkAppService中由ServiceHandler处理,启动StkMenuActivity
- 用户进行一些操作,由ServiceHandler处理,通过CatResponseMessage向CatService发送消息
- CatService通过广播消息返回,由StkCmdReceiver接收,启动StkAppService,并携带广播中的参数,再加上OP_CMD参数,将由ServiceHandler进行处理
3.1 响应CatService发来的指令
以GET_INPUT命令为例,由StkCmdReceiver.java
接收
public class StkCmdReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (AppInterface.CAT_CMD_ACTION.equals(action)) { handleAction(context, intent, StkAppService.OP_CMD); } ...//后面还有其它的一些广播处理 } private void handleAction(Context context, Intent intent, int op) { Bundle args = new Bundle(); //获取卡槽slot_id int slot_id = intent.getIntExtra(StkAppService.SLOT_ID, 0); //传入op参数为OP_CMD,表示CAT发来的指令,本节中以GET_INPUT为例 args.putInt(StkAppService.OPCODE, op); //传入slot_id args.putInt(StkAppService.SLOT_ID, slot_id); if (StkAppService.OP_CMD == op) { //传入具体的command参数 args.putParcelable(StkAppService.CMD_MSG, intent.getParcelableExtra(StkAppService.STK_CMD)); } ... //启动StkAppService,并传入参数 Intent toService = new Intent(context, StkAppService.class); toService.putExtras(args); context.startService(toService); } }
StkAppService.java
处理:
@Override public void onStart(Intent intent, int startId) { //发送消息由ServiceHandler处理 Message msg = mServiceHandler.obtainMessage(); msg.arg1 = op; msg.arg2 = slotId; msg.obj = args.getParcelable(CMD_MSG); mServiceHandler.sendMessage(msg); }
ServiceHandler
处理消息:
private final class ServiceHandler extends Handler { @Override public void handleMessage(Message msg) { if(null == msg) { CatLog.d(LOG_TAG, "ServiceHandler handleMessage msg is null"); return; } int opcode = msg.arg1; int slotId = msg.arg2; // SPRD: Add this condition for slotId is invalid. if (slotId < 0) { CatLog.d(LOG_TAG, "ServiceHandler handleMessage slotId is invalid"); return; } CatLog.d(LOG_TAG, "handleMessage opcode[" + opcode + "], sim id[" + slotId + "]"); if (opcode == OP_CMD && msg.obj != null && ((CatCmdMessage)msg.obj).getCmdType()!= null) { CatLog.d(LOG_TAG, "cmdName[" + ((CatCmdMessage)msg.obj).getCmdType().name() + "]"); } mStkContext[slotId].mOpCode = opcode; switch (opcode) { ... case OP_CMD: CatLog.d(LOG_TAG, "[OP_CMD]"); CatCmdMessage cmdMsg = (CatCmdMessage) msg.obj; /* 有两种类型的命令: Interactive 交互命令,需要用户的交互操作 Informative 展示一条信息,不需要用户操作 交互命令不可以被覆盖,如果当前有交互命令正在执行,下一条命令需要等待 当前命令执行完成或者超时 非交互命令可以很快得得到响应 */ if (!isCmdInteractive(cmdMsg)) { //非交互命令由handleCmd处理 handleCmd(cmdMsg, slotId); } else { if (!mStkContext[slotId].mCmdInProgress) { mStkContext[slotId].mCmdInProgress = true; //交互命令,如果当前没有正在处理的命令,立即调用handleCmd处理 handleCmd((CatCmdMessage) msg.obj, slotId); } else { CatLog.d(LOG_TAG, "[Interactive][in progress]"); //交互命令,当前正在处理,则命令进入排队 mStkContext[slotId].mCmdsQ.addLast(new DelayedCmd(OP_CMD, (CatCmdMessage) msg.obj, slotId)); } } break; } } /**判断是否是交互命令 */ private boolean isCmdInteractive(CatCmdMessage cmd) { switch (cmd.getCmdType()) { case SEND_DTMF: case SEND_SMS: case SEND_SS: case SEND_USSD: case SET_UP_IDLE_MODE_TEXT: case SET_UP_MENU: case CLOSE_CHANNEL: case RECEIVE_DATA: case SEND_DATA: case SET_UP_EVENT_LIST: case REFRESH: return false; } return true; }
下来继续来看handleCmd方法,以GET_INPUT为例:
private void handleCmd(CatCmdMessage cmdMsg, int slotId) { if (cmdMsg == null) { return; } ... switch (cmdMsg.getCmdType()) { case GET_INPUT: case GET_INKEY: launchInputActivity(slotId); //接收到GET_INPUT指令启动InputActivity break; } } private static final String STK_INPUT_ACTIVITY_NAME = PACKAGE_NAME + ".StkInputActivity"; //启动StkInputActivity并传入参数 private void launchInputActivity(int slotId) { Intent newIntent = new Intent(Intent.ACTION_VIEW); String targetActivity = STK_INPUT_ACTIVITY_NAME; String uriString = STK_INPUT_URI + System.currentTimeMillis(); //Set unique URI to create a new instance of activity for different slotId. Uri uriData = Uri.parse(uriString); CatLog.d(LOG_TAG, "launchInputActivity, slotId: " + slotId); newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | getFlagActivityNoUserAction(InitiatedByUserAction.unknown, slotId)); newIntent.setClassName(PACKAGE_NAME, targetActivity); newIntent.putExtra("INPUT", mStkContext[slotId].mCurrentCmd.geInput()); newIntent.putExtra(SLOT_ID, slotId); newIntent.setData(uriData); mContext.startActivity(newIntent); }
到此,GET_INPUT指令就执行完了。后面等待用户输入完成,点击确认后,就是用户发送响应的流程了,下面继续讲。
3.2 响应用户操作,发送消息给CatService
上面讲到了启动StkInputActivity,下面继续看:
我们直接跳到用户输入完成后,点击确认按键:
public void onClick(View v) { String input = null; if (!mAcceptUsersInput) { CatLog.d(LOG_TAG, "mAcceptUsersInput:false"); return; } switch (v.getId()) { case R.id.button_ok: // Check that text entered is valid . if (!verfiyTypedText()) { CatLog.d(LOG_TAG, "handleClick, invalid text"); return; } mAcceptUsersInput = false; //获取用户输入内容 input = mTextIn.getText().toString(); break; } CatLog.d(LOG_TAG, "handleClick, ready to response"); //调用sendResponse方法发送文本,响应消息类型为RES_ID_INPUT sendResponse(StkAppService.RES_ID_INPUT, input, false); } //发送文本消息给CAT void sendResponse(int resId, String input, boolean help) { //传入参数 mIsResponseSent = true; Bundle args = new Bundle(); //传入OPCODE类型为OP_RESPONSE args.putInt(StkAppService.OPCODE, StkAppService.OP_RESPONSE); args.putInt(StkAppService.SLOT_ID, mSlotId); args.putInt(StkAppService.RES_ID, resId); if (input != null) { args.putString(StkAppService.INPUT, input); } args.putBoolean(StkAppService.HELP, help); //启动StkAppService mContext.startService(new Intent(mContext, StkAppService.class) .putExtras(args)); }
原来发回复消息也是通过StkAppService的。
StkService的流程跟之前的相同,也是启动通过ServiceHandler来处理的,直接看ServiceHandler的处理方法:
private final class ServiceHandler extends Handler { @Override public void handleMessage(Message msg) { switch (opcode) { case OP_RESPONSE: //OPCODE为OP_RESPONSE handleCmdResponse((Bundle) msg.obj, slotId); break; } } //处理OP_RESPONSE的OPCODE private void handleCmdResponse(Bundle args, int slotId) { CatResponseMessage resMsg = new CatResponseMessage(mStkContext[slotId].mCurrentCmd); switch(args.getInt(RES_ID)) { case RES_ID_INPUT: //响应消息类型 CatLog.d(LOG_TAG, "RES_ID_INPUT"); String input = args.getString(INPUT); resMsg.setResultCode(mStkContext[slotId].mCurrentCmd.hasIconLoadFailed() ResultCode.PRFRMD_ICON_NOT_DISPLAYED : ResultCode.OK); resMsg.setInput(input); //写消息内容 break; } onCmdResponse(resMsg, slotId);//通知CAT有response消息 } private void onCmdResponse(CatResponseMessage resMsg, int slotId){ if(mStkService[slotId] == null){ CatLog.d(LOG_TAG, "mStkService[" + slotId + "] is null, reget it from CatServiceSprd"); mStkService[slotId] = CatService.getInstance(slotId); } if (mStkService[slotId] == null) { // This should never happen (we should be responding only to a message // that arrived from StkService). It has to exist by this time CatLog.d(LOG_TAG, "Exception! mStkService is null when we need to send response."); }else{ //最终调用CatService的onCmdResponse方法 mStkService[slotId].onCmdResponse(resMsg); }
到此,用户响应消息也执行完成,后面的就是到framework了。从最后执行的代码看到,由CatService发出CatResponseMessage消息,该类位于frameworks/opt/telephony/src/java/com/android/internal/telephony/cat/CatResponseMessage.java
frameworks/opt/telephony/src/java/com/android/internal/telephony/cat/CatService.java