android之Sim Tool Kit流程分析

一.簡介

      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中的消息類型

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   接收廣播


 
發佈了56 篇原創文章 · 獲贊 40 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章