一.簡介
STK 或者 UTK 就是 Sim Tool Kit (sim卡工具包),定製了一系列與運營商相關的應用(查詢天氣,話費,彩鈴等),可以理解爲安裝在SIM卡上的應用。運營商將相關應用信息保存在SIM卡中,STK應用需要從SIM卡中讀取相關應用信息,SIM卡也會向STK應用主動上報應用信息。
我們知道SIM卡是插在Modem中的,要讀取SIM卡的內容,就必須要經過Modem層,而與Modem層進行交互離不開AT指令。在framework中,發送AT指令主要是在RIL中完成的。所以讀取SIM卡的內容的流程可以歸結爲:STK應用---->RILJ---->RILC---->Modem---->運營商的基站。而這裏只需要跟蹤一下STK應用到RIL的RILJ層就可以了,以後有空可以研究一下RILC層的代碼。既然最後到達的是RIL層,那麼我們從RIL層往STK應用層反推它的流程。看看從SIM卡中讀取的信息是怎樣到達STK應用的。在我們插卡開機的時候,Modem檢測到有卡插入,這時候它會讀取SIM中的相關信息,並把消息上報給RIL層。Modem上報消息的過程這裏先不管。我們只研究RIL,要研究RIL層,我們需要先了解一下RIL層的消息分類。
二.RIL中的消息類型
solicited response message: 比如撥號,接聽,掛斷等這些做主動請求的操作的消息
unsolicited response message:GSM/GPRS Modem硬件模塊主動上報的,例如來電,接收短信,基站信息等消息。是從硬件來的消息,RIL層被動接收。
STK應用在RIL中的消息有:
solicited:
RIL_REQUEST_STK_GET_PROFILE
RIL_REQUEST_STK_SET_PROFILE
RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND // 打開子菜單
RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE //獲取子菜單信息
RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM
RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING
RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS
UnSolicited:
RIL_UNSOL_STK_SESSION_END
RIL_UNSOL_STK_PROACTIVE_COMMAND // 顯示菜單
RIL_UNSOL_STK_EVENT_NOTIFY
RIL_UNSOL_STK_CALL_SETUP
無論是哪一種消息,從Modem處上傳上來的信息,它的處理過程大體一致。我們只需要沿着具體一個消息的進行追蹤,就可以弄清楚它的流程走向。這裏我們研究一下UnSolicited的這個消息RIL_UNSOL_STK_PROACTIVE_COMMAND。
三.流程分析
在插卡開機的時候,Modem讀取此SIM卡中STK顯示的菜單,並向RIL層發送RIL_UNSOL_STK_PROACTIVE_COMMAND消息。這個消息被RILReceiver捕獲並處理,從這裏開始。流程分三個部分,分別是:1.RILJ部分。2.Framework部分。3.STK部分(應用層)
1.RILJ部分
RILReceiver是RIL的內部類,負責監聽Socket消息,從Socket中接收並處理RILC上報的消息。(RILJ與RILC的通信主要通過Socket)其邏輯是:
一. 維護Socket的連接。
二. 把從Socket中讀取到的消息交給RIL中processResponse()方法處理。
這個方法主要是根據消息的兩種類型,做分別的處理。Parcel存儲着從Socket中讀取的信息。
Parcel:Parcel就是一個存放讀取數據的容器, Android系統中的binder進程間通信(IPC)就使用了Parcel類來進行客戶端與服務端數據的交互,而且AIDL的數據也是通過Parcel來交互的。在Java空間和C++都實現了Parcel,由於它在C/C++中,直接使用了內存來讀取數據,因此,它更有效率。
private void processResponse (Parcel p) {
int type;
type = p.readInt();
if (type == RESPONSE_UNSOLICITED) {
processUnsolicited (p);
} else if (type == RESPONSE_SOLICITED) {
RILRequest rr = processSolicited (p);
if (rr != null) {
rr.release();
decrementWakeLock();
}
}
}
主要研究UnSolicited消息,所以來看看processUnsolicited做了什麼。
protected void processUnsolicited (Parcel p) {
int response;
Object ret;
response = p.readInt(); //獲取UnSolicited Response消息類型
try {switch(response) { //根據UnSolicited Response消息類型獲取不同的ret對象
....
case RIL_UNSOL_STK_SESSION_END: ret = responseVoid(p); break;
case RIL_UNSOL_STK_PROACTIVE_COMMAND: ret = responseString(p); break;
case RIL_UNSOL_STK_EVENT_NOTIFY: ret = responseString(p); break;
case RIL_UNSOL_STK_CALL_SETUP: ret = responseInts(p); break;
...
}
switch(response) { //根據UnSolicited Response消息類型發出不同的消息通知
...
case RIL_UNSOL_STK_PROACTIVE_COMMAND:
if (RILJ_LOGD) unsljLogRet(response, ret);
if (mCatProCmdRegistrant != null) {
mCatProCmdRegistrant.notifyRegistrant(
new AsyncResult (null, ret, null));
}
break;
...
}
}
2.Framework部分
這個notifyRegistrant()方法是調用的哪個類裏邊的呢。我們來看看mCatProCmdRegistrant這個變量:protected Registrant mCatProCmdRegistrant;
Registrant.java
public void notifyRegistrant(AsyncResult ar) {
internalNotifyRegistrant (ar.result, ar.exception);
}
void internalNotifyRegistrant (Object result, Throwable exception) {
Handler h = getHandler();
if (h == null) {
clear();
} else {
Message msg = Message.obtain();
msg.what = what;
msg.obj = new AsyncResult(userObj, result, exception);
h.sendMessage(msg);
}
}
很明顯,這個Registrant.java是Android裏的消息處理機制,用一個類把Handler發送消息的操作封裝起來,並做了異步的處理。當需要此Handler發送消息時,只需要調用這個消息通知的方法notifyRegistrant()即可。
那麼,mCatProCmdRegistrant這個變量是怎麼初始化的呢?由於RIL.java繼承了BaseCommands.java,在這個方法中,定義了mCatProCmdRegistrant變量並提供了set方法。
BaseCommands.java
protected Registrant mCatProCmdRegistrant;
@Override
public void setOnCatProactiveCmd(Handler h, int what, Object obj) {
mCatProCmdRegistrant = new Registrant (h, what, obj);
}
而調用這個setOnCatProactiveCmd()方法的,在CatService.java中,如下:CatService.java的構造方法不僅僅設置了MSG_ID_PROACTIVE_COMMAND這個消息的Handler,還設置了其他消息的Handler。
CatService.java
構造方法:
mCmdIf.setOnCatSessionEnd(this, MSG_ID_SESSION_END, null);
mCmdIf.setOnCatProactiveCmd(this, MSG_ID_PROACTIVE_COMMAND, null);//Handler的what標示是MSG_ID_PROACTIVE_COMMAND
mCmdIf.setOnCatEvent(this, MSG_ID_EVENT_NOTIFY, null);
mCmdIf.setOnCatCallSetUp(this, MSG_ID_CALL_SETUP, null);
好了,到這裏我們先整理一下:
插卡開機,Medem讀取SIM卡信息,上報RIL_UNSOL_STK_PROACTIVE_COMMAND消息。RIlJ中的RILReceiver類進行接收並交給processUnsolicited()方法處理。processUnsolicited()方法根據消息類型通知對應的Handler處理()。而這些Handler的處理在CatService.java的handleMessage()方法中,如下:
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ID_PROACTIVE_COMMAND:
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;
}
}
}
if (lastCmd != null && data != null && lastCmd.equals(data)) {
CatLog.d(this, "<" + mPhoneId + ">" + "duplicate command, ignored !!");
break;
}
if (data != null) {
lastCmd = new String(data);
} else {
lastCmd = null;
}
if(MSG_ID_PROACTIVE_COMMAND == msg.what && null != data) {
if((mSetupMenuFlag&(1<<mPhoneId))==0 && isSetupMenuCMD(data)) {
saveSetupMenuData(data);
}
}
mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data));
break;
case MSG_ID_RIL_MSG_DECODED:
handleRilMsg((RilMessage) msg.obj);
break;
}
查看HandleMessage()方法中的MSG_ID_PROACTIVE_COMMAND消息處理。最後會調用mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data))這個方法:
RilMessageDecoder.java
public void sendStartDecodingMessageParams(RilMessage rilMsg) {
Message msg = obtainMessage(CMD_START);
msg.obj = rilMsg;
sendMessage(msg);
}
private class StateStart extends State {
@Override
public boolean processMessage(Message msg) {
if (msg.what == CMD_START) {
if (decodeMessageParams((RilMessage)msg.obj)) {
transitionTo(mStateCmdParamsReady);
}
} else {
CatLog.d(this, "StateStart unexpected expecting START=" +
CMD_START + " got " + msg.what);
}
return true;
}
}
這個方法最後也是通過發送CMD_START消息,交給StateStart.java處理,decodeMessageParams()這個方法主要是解碼,具體的解碼過程需要查看Bertlv.java裏的代碼。從CMD_START消息的發送到解碼結束這個過程,流程有點複雜,這裏就不做具體的分析了,解碼結束後,會發送MSG_ID_RIL_MSG_DECODED消息,查看CatService.java的handleMessage()方法,是的,它又回到了CatService.java中。在這個類中,頻繁的使用了Handler的消息機制,我們只要關注好Handler的what標識就可以了。接下來會執行handleRilMsg()方法:
private void handleRilMsg(RilMessage rilMsg) {
case MSG_ID_PROACTIVE_COMMAND:
try {
cmdParams = (CommandParams) rilMsg.mData;
} catch (ClassCastException e) {
// for error handling : cast exception
CatLog.d(this, "Fail to parse proactive command");
sendTerminalResponse(mCurrntCmd.mCmdDet, ResultCode.CMD_DATA_NOT_UNDERSTOOD,
false, 0x00, null);
break;
}
if (cmdParams != null) {
if (rilMsg.mResCode == ResultCode.OK ||
// ignore icon problem
rilMsg.mResCode == ResultCode.PRFRMD_ICON_NOT_DISPLAYED) {
handleProactiveCommand(cmdParams);
} else {
// for proactive commands that couldn't be decoded
// successfully respond with the code generated by the
// message decoder.
sendTerminalResponse(cmdParams.mCmdDet, rilMsg.mResCode,
false, 0, null);
}
}
break;
}
查看handleProactiveCommand()最後的一段代碼,發現它發送一個廣播,那這個廣播是誰接收的呢?查看AppInterface.CAT_CMD_ACTION這個變量存儲的包名是:android.intent.action.stk.command。
private void handleProactiveCommand(CommandParams cmdParams) {
Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
intent.putExtra("STK CMD", cmdMsg);
intent.putExtra("phone_id", mPhoneId);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcast(intent);
}
通過搜索這個包名,我們可以追溯到了STK應用中的AndroidManifest.xml中,看看這裏,定義了一個receiver來接收來自framework中的廣播。至此,流程跟蹤到了STk應用層。
3.STK部分(應用層)
AndroidManifest.xml:
<receiver android:name="com.android.stk.StkCmdReceiver" >
<intent-filter>
<action android:name="android.intent.action.stk.command" />
<action android:name="android.intent.action.stk.session_end" />
<action android:name="android.intent.action.stk.event" />
<action android:name="android.intent.action.LOCALE_CHANGED" />
</intent-filter>
</receiver>
StkCmdReceiver主要負責從接收來自framework中的廣播,並啓動StkAppService服務。
public class StkCmdReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
int phoneId = intent.getIntExtra(StkAppService.PHONE_ID, 0);
if(action.equals(AppInterface.CAT_CMD_EVENT) || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
handleEventDownload(context, intent);
return;
}
if (phoneId == StkAppService.PHONE_ID_NUM) {
if (action.equals(AppInterface.CAT_CMD_ACTION)) {
handleCommandMessage(context, intent);
} else if (action.equals(AppInterface.CAT_SESSION_END_ACTION)) {
handleSessionEnd(context, intent);
}
}
}
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));
}
}
這個StkAppService是STK中的核心服務,它主要負責調度各個顯示層顯示相應的數據。
StkAppService.java
public void onStart(Intent intent, int startId) {
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;
}
...
mServiceHandler.sendMessage(msg);
}
private final class ServiceHandler extends Handler {
@Override
public void handleMessage(Message msg) {
int opcode = msg.arg1;
switch (opcode) {
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;
}
}
}
onStart()方法經過簡單的處理,然後交給內部類ServiceHandler進行處理,根據類型的不同,交由不同的Activity顯示。比如,這裏交由handleCmd()方法:
private void handleCmd(CatCmdMessage cmdMsg) {
if (cmdMsg == null) {
return;
}
// save local reference for state tracking.
mCurrentCmd = cmdMsg;
CommandDetails cmddet = cmdMsg.getCmdDet();
boolean waitForUsersResponse = true;
CatLog.d(this, cmdMsg.getCmdType().name());
switch (cmdMsg.getCmdType()) {
case DISPLAY_TEXT:
….
case SELECT_ITEM:
mCurrentMenu = cmdMsg.getMenu();
isSendSS = false;
//launchMenuActivity(cmdMsg.getMenu());
if (phoneIsIdle()) {
launchMenuActivity(cmdMsg.getMenu());
CatLog.d(this,"SELECT_ITEM start StkMenuActivity");
} else {
CatLog.d(this, "SELECT_ITEM is on call, mMenuIsVisibile = " + mMenuIsVisibile);
if (mMenuIsVisibile) {
launchMenuActivity(cmdMsg.getMenu());
CatLog.d(this,"mMenuIsVisibile start StkMenuActivity");
}
return;
}
break;
}
}
啓動某個Activity顯示:
private void launchMenuActivity(Menu menu) {
Intent newIntent = new Intent(Intent.ACTION_VIEW);
newIntent.setClassName(PACKAGE_NAME, MENU_ACTIVITY_NAME);
int intentFlags = Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP;
if (menu == null) {
// We assume this was initiated by the user pressing the tool kit icon
intentFlags |= getFlagActivityNoUserAction(InitiatedByUserAction.yes);
newIntent.putExtra("STATE", StkMenuActivity.STATE_MAIN);
} else {
// We don't know and we'll let getFlagActivityNoUserAction decide.
intentFlags |= getFlagActivityNoUserAction(InitiatedByUserAction.unknown);
newIntent.putExtra("STATE", StkMenuActivity.STATE_SECONDARY);
}
newIntent.setFlags(intentFlags);
CatLog.d(this, "launchMenuActivity: intent=" + newIntent + ", mCurrentMenu="+ mCurrentMenu);
mContext.startActivity(newIntent);
mStkMenuRef = true;
}
至此,開機顯示STK信息的流程就追蹤結束了。當然STK應用層面的東西還是很多的,在這一章就不打算再分析了,如果後面有時間,再繼續看看。
StkLauncherActivity.java:入口類
StkMenuActivity.java 主頁面(信息列表)
StkInputActivity.java 輸入頁面
StkDialogActivity.java 對話框頁面
StkAppInstaller.java 這個類主要用來設置是否在桌面上顯示這個STK圖標
StkCmdReceiver.java 接收廣播