Android N SIM 卡信息加載流程

    在 PhoneAPP 啓動關係類初始化中,我們提到監聽處理SIM卡狀態的兩個關鍵類 UiccController 和 IccCardProxy,那麼他們與SIM卡信息究竟是如何交互的呢?在UiccController 的 Android 源碼中有這樣一個 SIM卡相關類關係說明,這裏我們先整體上看一下

/**
 * This class is responsible for keeping all knowledge about
 * Universal Integrated Circuit Card (UICC), also know as SIM's,
 * in the system. It is also used as API to get appropriate
 * applications to pass them to phone and service trackers.
 *
 * UiccController is created with the call to make() function.
 * UiccController is a singleton and make() must only be called once
 * and throws an exception if called multiple times.
 *
 * Once created UiccController registers with RIL for "on" and "unsol_sim_status_changed"
 * notifications. When such notification arrives UiccController will call
 * getIccCardStatus (GET_SIM_STATUS). Based on the response of GET_SIM_STATUS
 * request appropriate tree of uicc objects will be created.
 *
 * Following is class diagram for uicc classes:
 *
 *                       UiccController
 *                            #
 *                            |
 *                        UiccCard
 *                          #   #
 *                          |   ------------------
 *                    UiccCardApplication    CatService
 *                      #            #
 *                      |            |
 *                 IccRecords    IccFileHandler
 *                 ^ ^ ^           ^ ^ ^ ^ ^
 *    SIMRecords---- | |           | | | | ---SIMFileHandler
 *    RuimRecords----- |           | | | ----RuimFileHandler
 *    IsimUiccRecords---           | | -----UsimFileHandler
 *                                 | ------CsimFileHandler
 *                                 ----IsimFileHandler
 *
 * Legend: # stands for Composition
 *         ^ stands for Generalization
 *
 * See also {@link com.android.internal.telephony.IccCard}
 * and {@link com.android.internal.telephony.uicc.IccCardProxy}
 */     

UiccController:整個UICC事務處理的入口,負責對外提供IccRecords、IccFileHandler、UiccCardApplication等對象,並完成整個UICC系統的初始化工作。

UiccCard: 本身並不實現具體的功能,只負責UiccCardApplication及CatService對象的創建更新工作,以及當SIM卡被插入或者拔出時彈出提示框是否需要重啓設備。

UiccCardApplication:主要任務包括創建並向外提供IccFileHandler、IccRecords對象、提供對SIM卡狀態的監聽等

IccRecords:提供SIM卡常用信息查詢,如IMSI、ICCID、VoiceMail、Contacts等,UiccController根據SIM類型不同,創建不同IccRecords對象

IccFileHandler:負責SIM卡文件系統的讀寫,會在UiccCardApplication中根據SIM類型不同,創建不同IccFileHandler對象

CatService:負責 STK 相關業務,接收解析 modem 上傳的相關數據,並回傳給RIL及StkAppService


1、SIM卡信息加載流程圖


2、SIM卡狀態監聽及創建

      在 PhoneAPP 初始化時,PhoneFactory 創建了 UiccController 和 GsmCdmaPhone,前者以RIL爲觀察對象直接註冊監聽卡的狀態變化,後者構造出 IccCardProxy 間接向UiccController 註冊監聽卡狀態變化,進行相關事件處理。

      在插卡開機或熱插拔時,當 Modem 檢卡成功後會主動上報 RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGE 消息,通過 RIL 通知上層監聽者 UiccController,後者在收到通知後會調用 getIccCardStatus 通過 RIL 向 Modem 主動獲取SIM卡相關信息,在得到 RIL_REQUEST_GET_SIM_STATUS 返回後,調用 onGetIccCardStatusDone 進行後續處理。

frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/UiccController.java

    @Override
    public void handleMessage (Message msg) {
        synchronized (mLock) {
            Integer index = getCiIndex(msg);
            AsyncResult ar = (AsyncResult)msg.obj;
            switch (msg.what) {
                case EVENT_ICC_STATUS_CHANGED:
                    if (DBG) log("Received EVENT_ICC_STATUS_CHANGED, calling getIccCardStatus");
                    mCis[index].getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE, index));
                    break;
                case EVENT_GET_ICC_STATUS_DONE:
                    if (DBG) log("Received EVENT_GET_ICC_STATUS_DONE");
                    onGetIccCardStatusDone(ar, index);
                    break;
                 ......

        在onGetIccCardStatusDone函數中,UiccController將判斷是否存在對應的UiccCard,如果沒有將根據卡信息創建出對應的UiccCard對象;否則,僅進行UiccCard的更新操作。實際上,從代碼邏輯看,即使新建UiccCard對象,最終也會調用到 Update 函數。

    private synchronized void onGetIccCardStatusDone(AsyncResult ar, Integer index) {
        if (ar.exception != null) {
            return;
        }
        if (!isValidCardIndex(index)) {
            Rlog.e(LOG_TAG,"onGetIccCardStatusDone: invalid index : " + index);
            return;
        }

        IccCardStatus status = (IccCardStatus)ar.result;

        if (mUiccCards[index] == null) {
            //Create new card
            mUiccCards[index] = new UiccCard(mContext, mCis[index], status, index);
        } else {
            //Update already existing card
            mUiccCards[index].update(mContext, mCis[index] , status);
        }

        if (DBG) log("Notifying IccChangedRegistrants");
        mIccChangedRegistrants.notifyRegistrants(new AsyncResult(null, index, null));

    }

查看代碼,UiccCard.update 最終完成了UiccCardApplication 和 CatService 的創建和更新

frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/UiccCard.java

    public void update(Context c, CommandsInterface ci, IccCardStatus ics) {
           ......
           //創建/更新UiccCardApplication
            for ( int i = 0; i < mUiccApplications.length; i++) {
                if (mUiccApplications[i] == null) {
                    //Create newly added Applications
                    if (i < ics.mApplications.length) {
                        mUiccApplications[i] = new UiccCardApplication(this,
                                ics.mApplications[i], mContext, mCi);
                    }
                } else if (i >= ics.mApplications.length) {
                    //Delete removed applications
                    mUiccApplications[i].dispose();
                    mUiccApplications[i] = null;
                } else {
                    //Update the rest
                    mUiccApplications[i].update(ics.mApplications[i], mContext, mCi);
                }
            }
            // 創建/更新 CatService
            createAndUpdateCatService();
            ......
    }

    UiccCardApplication 的創建和更新邏輯主要包含了 IccFileHandler 和 IccRecords 對象的創建, 而實際獲取的對象都是根 Modem 返回的卡類型有關,具體代碼如下。至此,我們就創建了與Modem返回類型匹配的 SIM卡對象,當IccRecords被創建後,同樣將作爲RIL的觀察者,進行 registerForIccRefresh ,監聽處理 EVENT_REFRESH 消息。

PS: IccRecords與IccFileHandler的區別在於,前者以SIM存儲內容爲操作對象,偏重上層應用,後者以SIM文件系統爲操作對象,偏重底層實現。

frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java

  public UiccCardApplication(UiccCard uiccCard, IccCardApplicationStatus as, Context c, CommandsInterface ci) {
        ......
        mIccFh = createIccFileHandler(as.app_type);
        mIccRecords = createIccRecords(as.app_type, mContext, mCi);

       if (mAppState == AppState.APPSTATE_READY) {
            queryFdn();             // 讀取FDN數據
            queryPin1State();   // 查詢PIN狀態
        }
        mCi.registerForNotAvailable(mHandler, EVENT_RADIO_UNAVAILABLE, null);
    }

   // 根據modem返回的不同卡類型,創建不同 IccRecords 對象
    private IccRecords createIccRecords(AppType type, Context c, CommandsInterface ci) {
        if (type == AppType.APPTYPE_USIM || type == AppType.APPTYPE_SIM) {
            return new SIMRecords(this, c, ci);
        } else if (type == AppType.APPTYPE_RUIM || type == AppType.APPTYPE_CSIM){
            return new RuimRecords(this, c, ci);
        } else if (type == AppType.APPTYPE_ISIM) {
            return new IsimUiccRecords(this, c, ci);
        } else {
            // Unknown app type (maybe detection is still in progress)
            return null;
        }
    }
   // 根據modem返回的不同卡類型,創建不同 IccFileHandler 對象
    private IccFileHandler createIccFileHandler(AppType type) {
        switch (type) {
            case APPTYPE_SIM:
                return new SIMFileHandler(this, mAid, mCi);
            case APPTYPE_RUIM:
                return new RuimFileHandler(this, mAid, mCi);
            case APPTYPE_USIM:
                return new UsimFileHandler(this, mAid, mCi);
            case APPTYPE_CSIM:
                return new CsimFileHandler(this, mAid, mCi);
            case APPTYPE_ISIM:
                return new IsimFileHandler(this, mAid, mCi);
            default:
                return null;
        }
    } 


3、SIM卡信息加載

      在 SIM 卡對象創建時,會進行系列狀態及廣播的監聽,便於在合適的實際進行具體信息的加載等處理,以 SIMRecords 爲例

frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/SIMRecords.java

    public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
        super(app, c, ci);

        mAdnCache = new AdnRecordCache(mFh);

        mVmConfig = new VoiceMailConstants();
        mSpnOverride = new SpnOverride();

        mRecordsRequested = false;  // No load request is made till SIM ready

        // recordsToLoad is set to 0 because no requests are made yet
        mRecordsToLoad = 0;

        mCi.setOnSmsOnSim(this, EVENT_SMS_ON_SIM, null);

        // Start off by setting empty state
        resetRecords();
        // 向UiccCardApplication 註冊監聽 READY 和 LOCKED狀態變化
        mParentApp.registerForReady(this, EVENT_APP_READY, null);
        mParentApp.registerForLocked(this, EVENT_APP_LOCKED, null);
        
        IntentFilter intentfilter = new IntentFilter();
        intentfilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
        c.registerReceiver(mReceiver, intentfilter);
    }

     SIMRecords 在收到 UiccCardApplication 通知的 EVENT_APP_READY 消息後,會調用 onReady 方法,進而調用實際 SIM卡信息的加載函數 fetchSimRecords。此外,RIL 返回的 EVENT_REFRESH 消息也會觸發 fetchSimRecords ,進行 SIM卡信息的加載,下面來具體看下這個函數

   protected void fetchSimRecords() {
        mRecordsRequested = true;

        mCi.getIMSIForApp(mParentApp.getAid(), obtainMessage(EVENT_GET_IMSI_DONE));
        mRecordsToLoad++;

        mFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE));
        mRecordsToLoad++;

        // FIXME should examine EF[MSISDN]'s capability configuration
        // to determine which is the voice/data/fax line
        new AdnRecordLoader(mFh).loadFromEF(EF_MSISDN, getExtFromEf(EF_MSISDN), 1,
                    obtainMessage(EVENT_GET_MSISDN_DONE));
        mRecordsToLoad++;

        // Record number is subscriber profile
        mFh.loadEFLinearFixed(EF_MBI, 1, obtainMessage(EVENT_GET_MBI_DONE));
        mRecordsToLoad++;

        mFh.loadEFTransparent(EF_AD, obtainMessage(EVENT_GET_AD_DONE));
        mRecordsToLoad++;

        // Record number is subscriber profile
        mFh.loadEFLinearFixed(EF_MWIS, 1, obtainMessage(EVENT_GET_MWIS_DONE));
        mRecordsToLoad++;


        // Also load CPHS-style voice mail indicator, which stores
        // the same info as EF[MWIS]. If both exist, both are updated
        // but the EF[MWIS] data is preferred
        // Please note this must be loaded after EF[MWIS]
        mFh.loadEFTransparent(
                EF_VOICE_MAIL_INDICATOR_CPHS,
                obtainMessage(EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE));
        mRecordsToLoad++;

        // Same goes for Call Forward Status indicator: fetch both
        // EF[CFIS] and CPHS-EF, with EF[CFIS] preferred.
        loadCallForwardingRecords();

        getSpnFsm(true, null);    // 獲取SIM卡SPN信息
        .......

        if (DBG) log("fetchSimRecords " + mRecordsToLoad + " requested: " + mRecordsRequested);
    }

     從代碼看,fetchSimRecords 調用後會通過 IccFileHandler 對 IMSI、ICCID、MSISDN等信息進行逐條 load,並利用 mRecordsToLoad++ 計數。同時在每次load信息返回後,在 handleMessage 進行處理時,調用 onRecordLoaded 對 mRecordsToLoad計數 -1,待得 mRecordsToLoad == 0 && mRecordsRequested == true 時調用 onAllRecordsLoaded 進行VoiceCall、MCCMNC、SimOperatorNumeric、SimCountry等的設置。

   @Override
    protected void onRecordLoaded() {
        // One record loaded successfully or failed, In either case
        // we need to update the recordsToLoad count
        mRecordsToLoad -= 1;
        if (mRecordsToLoad == 0 && mRecordsRequested == true) {
            onAllRecordsLoaded();
        } else if (mRecordsToLoad < 0) {
            loge("recordsToLoad <0, programmer error suspected");
            mRecordsToLoad = 0;
        }
    }

   @Override
    protected void onAllRecordsLoaded() {
        if (DBG) log("record load complete");

        Resources resource = Resources.getSystem();
        if (resource.getBoolean(com.android.internal.R.bool.config_use_sim_language_file)) {
            setSimLanguage(mEfLi, mEfPl);
        } else {
            if (DBG) log ("Not using EF LI/EF PL");
        }

        setVoiceCallForwardingFlagFromSimRecords();

        if (mParentApp.getState() == AppState.APPSTATE_PIN ||
               mParentApp.getState() == AppState.APPSTATE_PUK) {
            // reset recordsRequested, since sim is not loaded really
            mRecordsRequested = false;
            // lock state, only update language
            return ;
        }

        // Some fields require more than one SIM record to set

        String operator = getOperatorNumeric();
        if (!TextUtils.isEmpty(operator)) {
            log("onAllRecordsLoaded set 'gsm.sim.operator.numeric' to operator='" +
                    operator + "'");
            mTelephonyManager.setSimOperatorNumericForPhone(
                    mParentApp.getPhoneId(), operator);
            final SubscriptionController subController = SubscriptionController.getInstance();
            int subId = subController.getSubIdUsingPhoneId(mParentApp.getPhoneId());
            if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
                subController.setMccMnc(operator, subId);
                log("update icc_operator_numeric = " + operator + " subId = " + subId);
            }
        } else {
            log("onAllRecordsLoaded empty 'gsm.sim.operator.numeric' skipping");
        }

        if (!TextUtils.isEmpty(mImsi)) {
            log("onAllRecordsLoaded set mcc imsi" + (VDBG ? ("=" + mImsi) : ""));
            mTelephonyManager.setSimCountryIsoForPhone(
                    mParentApp.getPhoneId(), MccTable.countryCodeForMcc(
                    Integer.parseInt(mImsi.substring(0,3))));
        } else {
            log("onAllRecordsLoaded empty imsi skipping setting mcc");
        }

        setVoiceMailByCountry(operator);

        mRecordsLoadedRegistrants.notifyRegistrants(
            new AsyncResult(null, null, null));
    }

4、IccCardProxy 及對外廣播

      前面已經說過,UiccController 是整個UICC框架的入口與控制者,對於卡狀態的維護和更新它都會做第一手的處理。那麼,IccCardProxy 又是負責什麼呢?從代碼看,IccCardProxy 會從UiccController 獲取UiccCard、UiccCardApplication、IccRecords實例,並註冊系列SIM卡狀態變化的監聽,從這點說,和UiccController 功能沒多少區別。當然,IccCardProxy同時還負責了對SIM卡狀態向外廣播及部分屬性值的設置更新工作。看下IccCardProxy 的創建流程:與Phone實例個數相對應,在 Phone 對象創建時被構造。考慮到,UiccController裏的UiccCard對象是跟卡動態關聯的,所以, 通過phone.getIccCard獲取 IccCardProxy對象,來進行卡狀態查詢等操作會更方便些,直接操作UiccController帶來一些隱患,如獲取不到卡狀態等問題。實際上,IccCardProxy 就是一個用來提供給外部使用的接口類,可由Phone直接調用進行相關卡信息查詢等,一般外部APP要獲取卡信息,都會通過 Phone 相關服務對外提供的接口間接操作,如 TelephonyManager。

      下面我們詳細看下 IccCardProxy 構造函數,不難發現 IccCardProxy 在通過UiccController 註冊 ICC_CARD_STATUS_CHANGED 消息時,沒有關聯 phoneId 信息,這裏UiccController雖是單例,但其內部的UiccCard卻可能會是多個,那麼無論哪個卡有更新,UiccController在更新完自己內部的UiccCard之後,都會通知所有 IccCardProxy 來更新各自內部的UiccCard實例等。

frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/IccCardProxy.java
    public IccCardProxy(Context context, CommandsInterface ci, int phoneId) {
        if (DBG) log("ctor: ci=" + ci + " phoneId=" + phoneId);
        mContext = context;
        mCi = ci;
        mPhoneId = phoneId;
        mTelephonyManager = (TelephonyManager) mContext.getSystemService(
                Context.TELEPHONY_SERVICE);
        mCdmaSSM = CdmaSubscriptionSourceManager.getInstance(context,
                ci, this, EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null);
        mUiccController = UiccController.getInstance();   // 單例模式
        mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
        ci.registerForOn(this,EVENT_RADIO_ON, null);
        ci.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_UNAVAILABLE, null);

        resetProperties();
        setExternalState(State.NOT_READY, false);
    }

       順便提一下這裏的 resetProperties,它在調用 TelephonyManager 接口設置屬性時,支持雙卡的方法:使用同一個key,同時保存兩個卡的屬性值,值之間使用","分隔,順序以phoneId 從小到大排序。使用時取出後將","分隔轉換爲數組直接取下標即可。

    void resetProperties() {
        if (mCurrentAppType == UiccController.APP_FAM_3GPP) {
            log("update icc_operator_numeric=" + "");
            mTelephonyManager.setSimOperatorNumericForPhone(mPhoneId, "");
            mTelephonyManager.setSimCountryIsoForPhone(mPhoneId, "");
            mTelephonyManager.setSimOperatorNameForPhone(mPhoneId, "");
         }
    }

      接着看 IccCardProxy對 ICC_CARD_STATUS_CHANGED 消息返回事件 EVENT_ICC_CHANGED 的處理,這裏主要調用了updateIccAvailability 函數,獲取/更新 UiccCard、UiccCardApplication、IccRecords實例,並向其註冊 SIM 狀態變化的各類監聽。

    private void updateIccAvailability() {
        synchronized (mLock) {
            // 獲取UiccCard
            UiccCard newCard = mUiccController.getUiccCard(mPhoneId);
            CardState state = CardState.CARDSTATE_ABSENT;
            UiccCardApplication newApp = null;
            IccRecords newRecords = null;
            if (newCard != null) {
                state = newCard.getCardState();
               // 獲取 UiccCardApplication
                newApp = newCard.getApplication(mCurrentAppType);
                if (newApp != null) {
                    // 獲取IccRecords   
                    newRecords = newApp.getIccRecords();
                }
            }

            if (mIccRecords != newRecords || mUiccApplication != newApp || mUiccCard != newCard) {
                if (DBG) log("Icc changed. Reregestering.");
                unregisterUiccCardEvents();
                mUiccCard = newCard;
                mUiccApplication = newApp;
                mIccRecords = newRecords;
                registerUiccCardEvents();
            }
            updateExternalState();
        }
    }

   // 向各實例註冊 SIM 狀態變化的監聽
    private void registerUiccCardEvents() {
        if (mUiccCard != null) {
            mUiccCard.registerForAbsent(this, EVENT_ICC_ABSENT, null);
        }
        if (mUiccApplication != null) {
            mUiccApplication.registerForReady(this, EVENT_APP_READY, null);
            mUiccApplication.registerForLocked(this, EVENT_ICC_LOCKED, null);
            mUiccApplication.registerForNetworkLocked(this, EVENT_NETWORK_LOCKED, null);
        }
        if (mIccRecords != null) {
            mIccRecords.registerForImsiReady(this, EVENT_IMSI_READY, null);
            mIccRecords.registerForRecordsLoaded(this, EVENT_RECORDS_LOADED, null);
            mIccRecords.registerForRecordsEvents(this, EVENT_ICC_RECORD_EVENTS, null);
        }
    }

     下面我們簡單看下幾個註冊事件返回消息的處理,在 UiccCardApplication 的註冊上,在卡狀態變成APPSTATE_READY/APPSTATE_PIN/APPSTATE_PUK/APPSTATE_SUBSCRIPTION_PERSO 等時會發出不同通知,讓 IccCardProxy進行相關狀態處理併發出廣播。以 READY 爲例,IccCardProxy 在收到 EVENT_APP_READY 消息後,會逐級調用到 setExternalState 發出廣播:TelephonyIntents.ACTION_SIM_STATE_CHANGED。

    private void setExternalState(State newState, boolean override) {
        synchronized (mLock) {
            if (mPhoneId == null || !SubscriptionManager.isValidSlotId(mPhoneId)) {
                loge("setExternalState: mPhoneId=" + mPhoneId + " is invalid; Return!!");
                return;
            }

            if (!override && newState == mExternalState) {
                loge("setExternalState: !override and newstate unchanged from " + newState);
                return;
            }
            mExternalState = newState;
            loge("setExternalState: set mPhoneId=" + mPhoneId + " mExternalState=" + mExternalState);
            mTelephonyManager.setSimStateForPhone(mPhoneId, getState().toString());

            // For locked states, we should be sending internal broadcast.
            if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(getIccStateIntentString(mExternalState))) {
                broadcastInternalIccStateChangedIntent(getIccStateIntentString(mExternalState),
                        getIccStateReason(mExternalState));
            } else {
                broadcastIccStateChangedIntent(getIccStateIntentString(mExternalState),
                        getIccStateReason(mExternalState));
            }
            // TODO: Need to notify registrants for other states as well.
            if ( State.ABSENT == mExternalState) {
                mAbsentRegistrants.notifyRegistrants();
            }
        }
    }

         仔細看代碼,我們發現 setExternalState 中除了broadcastIccStateChangedIntent,還有一個broadcastIccStateChangedIntent廣播方式,那麼二者有什麼不同呢?比較代碼,首先,從對外廣播看,二者發出的 Intent 有所差異,這直接影響接受者的註冊監聽。其次,二者 Intent 標誌位也有所不同,broadcastInternalIccStateChangedIntent 多了一個 FLAG_RECEIVER_REPLACE_PENDING。再看 broadcastIccStateChangedIntent,這裏多了個 mQuietMode 判斷(In case of 3gpp2 we need to find out if subscription used is coming from NV in which case we shouldn't broadcast any sim states changes)。

    private void broadcastIccStateChangedIntent(String value, String reason) {
        synchronized (mLock) {
            if (mPhoneId == null || !SubscriptionManager.isValidSlotId(mPhoneId)) {
                loge("broadcastIccStateChangedIntent: mPhoneId=" + mPhoneId
                        + " is invalid; Return!!");
                return;
            }
            if (mQuietMode) {   //  特殊情況,不發送廣播,具體見分析
                log("broadcastIccStateChangedIntent: QuietMode"
                        + " NOT Broadcasting intent ACTION_SIM_STATE_CHANGED "
                        + " value=" +  value + " reason=" + reason);
                return;
            }
            //LAFITEN-153
            //If card was pulled right after fetch sim began, simRecord whould send loaded. {
            if ((mUiccApplication == null
                    || mUiccApplication.getState() == AppState.APPSTATE_PIN
                    || mUiccApplication.getState() == AppState.APPSTATE_PUK)
                && (TextUtils.equals(IccCardConstants.INTENT_VALUE_ICC_LOADED, value)
                    || TextUtils.equals(IccCardConstants.INTENT_VALUE_ICC_IMSI, value)
                    || TextUtils.equals(IccCardConstants.INTENT_VALUE_ICC_READY, value))) {
                loge("Prevent Broadcasting intent ACTION_SIM_STATE_CHANGED " +  value
                    + " reason " + reason);
                return ;
            }
            //LAFITEN-153
            Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
            // TODO - we'd like this intent to have a single snapshot of all sim state,
            // but until then this should not use REPLACE_PENDING or we may lose
            // information
            // intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
            intent.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone");
            intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, value);
            intent.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason);
            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhoneId);
            log("broadcastIccStateChangedIntent intent ACTION_SIM_STATE_CHANGED value=" + value
                + " reason=" + reason + " for mPhoneId=" + mPhoneId);
            ActivityManagerNative.broadcastStickyIntent(intent, READ_PHONE_STATE,
                    UserHandle.USER_ALL);
        }
    }

    private void broadcastInternalIccStateChangedIntent(String value, String reason) {
        synchronized (mLock) {
            if (mPhoneId == null) {
                loge("broadcastInternalIccStateChangedIntent: Card Index is not set; Return!!");
                return;
            }
            Intent intent = new Intent(ACTION_INTERNAL_SIM_STATE_CHANGED);
            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING      // 新廣播發出後會替換被掛起的匹配的舊廣播
                    | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
            intent.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone");
            intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, value);
            intent.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason);
            intent.putExtra(PhoneConstants.PHONE_KEY, mPhoneId);  // SubId may not be valid.
            log("Sending intent ACTION_INTERNAL_SIM_STATE_CHANGED" + " for mPhoneId : " + mPhoneId);
            ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL);
        }
    }

      在使用場景上 broadcastInternalIccStateChangedIntent 主要用在 INTENT_VALUE_ICC_LOCKED 和 INTENT_VALUE_ICC_LOADED 狀態,其他狀態使用 broadcastIccStateChangedIntent,主要包括 READY、IMSI、LOADED。

    /* INTERNAL LOCKED means ICC is locked by pin or by network */
    public static final String INTENT_VALUE_ICC_INTERNAL_LOCKED = "INTERNAL_LOCKED";
    /* READY means ICC is ready to access */
    public static final String INTENT_VALUE_ICC_READY = "READY";
    /* IMSI means ICC IMSI is ready in property */
    public static final String INTENT_VALUE_ICC_IMSI = "IMSI";
    /* LOADED means all ICC records, including IMSI, are loaded */
    public static final String INTENT_VALUE_ICC_LOADED = "LOADED";
    /* The extra data for broadcasting intent INTENT_ICC_STATE_CHANGE */
    public static final String INTENT_KEY_LOCKED_REASON = "reason";
    /* PIN means ICC is locked on PIN1 */
    public static final String INTENT_VALUE_LOCKED_ON_PIN = "PIN";
    /* PUK means ICC is locked on PUK1 */
    public static final String INTENT_VALUE_LOCKED_ON_PUK = "PUK";
    /* NETWORK means ICC is locked on NETWORK PERSONALIZATION */
    public static final String INTENT_VALUE_LOCKED_NETWORK = "NETWORK";
    /* PERM_DISABLED means ICC is permanently disabled due to puk fails */
    public static final String INTENT_VALUE_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED";

        到了這裏,關於 IccCardProxy 及對外廣播 我們就說的差不多了,IccCardProxy 對於所註冊 SIM 狀態的返回事件處理主要就是一些屬性的設置及相應廣播的發出。最後,我們再看一下 mIccRecords.registerForRecordsLoaded 返回事件的處理,這裏主要更新了 MCC/MNC。

            case EVENT_RECORDS_LOADED:
                // Update the MCC/MNC.
                if (mIccRecords != null) {
                    Phone currentPhone = PhoneFactory.getPhone(mPhoneId);
                    String operator = currentPhone.getOperatorNumeric();
                    log("operator=" + operator + " mPhoneId=" + mPhoneId);

                    if (!TextUtils.isEmpty(operator)) {
                        mTelephonyManager.setSimOperatorNumericForPhone(mPhoneId, operator);
                        String countryCode = operator.substring(0,3);
                        if (countryCode != null) {
                            mTelephonyManager.setSimCountryIsoForPhone(mPhoneId,
                                    MccTable.countryCodeForMcc(Integer.parseInt(countryCode)));
                        } else {
                            loge("EVENT_RECORDS_LOADED Country code is null");
                        }
                    } else {
                        loge("EVENT_RECORDS_LOADED Operator name is null");
                    }
                }
                if (mUiccCard != null && !mUiccCard.areCarrierPriviligeRulesLoaded()) {
                    mUiccCard.registerForCarrierPrivilegeRulesLoaded(
                        this, EVENT_CARRIER_PRIVILIGES_LOADED, null);
                } else {
                    onRecordsLoaded();
                }
                break;




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章