content: /*
   * Copyright (C) 2006 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.internal.telephony.uicc;
  
  import android.annotation.UnsupportedAppUsage;
  import android.content.Context;
  import android.content.res.Resources;
  import android.os.AsyncResult;
  import android.os.Message;
  import android.os.PersistableBundle;
  import android.telephony.CarrierConfigManager;
  import android.telephony.PhoneNumberUtils;
  import android.telephony.Rlog;
  import android.telephony.SmsMessage;
  import android.telephony.SubscriptionInfo;
  import android.text.TextUtils;
  
  import com.android.internal.telephony.CommandsInterface;
  import com.android.internal.telephony.MccTable;
  import com.android.internal.telephony.SmsConstants;
  import com.android.internal.telephony.SubscriptionController;
  import com.android.internal.telephony.gsm.SimTlv;
  import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  
  /**
   * {@hide}
   */
  public class SIMRecords extends IccRecords {
      protected static final String LOG_TAG = "SIMRecords";
  
      private static final boolean CRASH_RIL = false;
  
      private static final boolean VDBG = false;
  
      // ***** Instance Variables
  
      @UnsupportedAppUsage
      VoiceMailConstants mVmConfig;
  
      // ***** Cached SIM State; cleared on channel close
  
      private int mCallForwardingStatus;
  
      /**
       * States only used by getSpnFsm FSM
       */
      private GetSpnFsmState mSpnState;
  
      /** CPHS service information (See CPHS 4.2 B.3.1.1)
       *  It will be set in onSimReady if reading GET_CPHS_INFO successfully
       *  mCphsInfo[0] is CPHS Phase
       *  mCphsInfo[1] and mCphsInfo[2] is CPHS Service Table
       */
      private byte[] mCphsInfo = null;
      boolean mCspPlmnEnabled = true;
  
      @UnsupportedAppUsage
      byte[] mEfMWIS = null;
      @UnsupportedAppUsage
      byte[] mEfCPHS_MWI =null;
      @UnsupportedAppUsage
      byte[] mEfCff = null;
      @UnsupportedAppUsage
      byte[] mEfCfis = null;
  
      @UnsupportedAppUsage
      byte[] mEfLi = null;
      @UnsupportedAppUsage
      byte[] mEfPl = null;
  
      @UnsupportedAppUsage
      UsimServiceTable mUsimServiceTable;
  
      @Override
      public String toString() {
          return "SimRecords: " + super.toString()
                  + " mVmConfig" + mVmConfig
                  + " callForwardingEnabled=" + mCallForwardingStatus
                  + " spnState=" + mSpnState
                  + " mCphsInfo=" + mCphsInfo
                  + " mCspPlmnEnabled=" + mCspPlmnEnabled
                  + " efMWIS=" + mEfMWIS
                  + " efCPHS_MWI=" + mEfCPHS_MWI
                  + " mEfCff=" + mEfCff
                  + " mEfCfis=" + mEfCfis
                  + " getOperatorNumeric=" + getOperatorNumeric();
      }
  
      // ***** Constants
  
      // From TS 51.011 EF[SPDI] section
      static final int TAG_SPDI = 0xA3;
      static final int TAG_SPDI_PLMN_LIST = 0x80;
  
      // Full Name IEI from TS 24.008
      static final int TAG_FULL_NETWORK_NAME = 0x43;
  
      // Short Name IEI from TS 24.008
      static final int TAG_SHORT_NETWORK_NAME = 0x45;
  
      // active CFF from CPHS 4.2 B.4.5
      static final int CFF_UNCONDITIONAL_ACTIVE = 0x0a;
      static final int CFF_UNCONDITIONAL_DEACTIVE = 0x05;
      static final int CFF_LINE1_MASK = 0x0f;
      static final int CFF_LINE1_RESET = 0xf0;
  
      // CPHS Service Table (See CPHS 4.2 B.3.1)
      private static final int CPHS_SST_MBN_MASK = 0x30;
      private static final int CPHS_SST_MBN_ENABLED = 0x30;
  
      // EF_CFIS related constants
      // Spec reference TS 51.011 section 10.3.46.
      private static final int CFIS_BCD_NUMBER_LENGTH_OFFSET = 2;
      private static final int CFIS_TON_NPI_OFFSET = 3;
      private static final int CFIS_ADN_CAPABILITY_ID_OFFSET = 14;
      private static final int CFIS_ADN_EXTENSION_ID_OFFSET = 15;
  
      // ***** Event Constants
      private static final int SIM_RECORD_EVENT_BASE = 0x00;
      private static final int EVENT_GET_IMSI_DONE = 3 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_ICCID_DONE = 4 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_MBI_DONE = 5 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_MBDN_DONE = 6 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_MWIS_DONE = 7 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE = 8 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_AD_DONE = 9 + SIM_RECORD_EVENT_BASE; // Admin data on SIM
      private static final int EVENT_GET_MSISDN_DONE = 10 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_CPHS_MAILBOX_DONE = 11 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_SPN_DONE = 12 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_SPDI_DONE = 13 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_UPDATE_DONE = 14 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_PNN_DONE = 15 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_SST_DONE = 17 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_ALL_SMS_DONE = 18 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_MARK_SMS_READ_DONE = 19 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_SET_MBDN_DONE = 20 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_SMS_ON_SIM = 21 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_SMS_DONE = 22 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_CFF_DONE = 24 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_SET_CPHS_MAILBOX_DONE = 25 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_INFO_CPHS_DONE = 26 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_SET_MSISDN_DONE = 30 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_CFIS_DONE = 32 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_CSP_CPHS_DONE = 33 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_GID1_DONE = 34 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_GID2_DONE = 36 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_PLMN_W_ACT_DONE = 37 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_OPLMN_W_ACT_DONE = 38 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_HPLMN_W_ACT_DONE = 39 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_EHPLMN_DONE = 40 + SIM_RECORD_EVENT_BASE;
      private static final int EVENT_GET_FPLMN_DONE = 41 + SIM_RECORD_EVENT_BASE;
  
      // TODO: Possibly move these to IccRecords.java
      private static final int SYSTEM_EVENT_BASE = 0x100;
      private static final int EVENT_APP_LOCKED = 2 + SYSTEM_EVENT_BASE;
      private static final int EVENT_APP_NETWORK_LOCKED = 3 + SYSTEM_EVENT_BASE;
  
  
      // ***** Constructor
  
      public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
          super(app, c, ci);
  
          mAdnCache = new AdnRecordCache(mFh);
  
          mVmConfig = new VoiceMailConstants();
  
          mRecordsRequested = false;  // No load request is made till SIM ready
          mLockedRecordsReqReason = LOCKED_RECORDS_REQ_REASON_NONE;
  
          // 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();
          mParentApp.registerForReady(this, EVENT_APP_READY, null);
          mParentApp.registerForLocked(this, EVENT_APP_LOCKED, null);
          mParentApp.registerForNetworkLocked(this, EVENT_APP_NETWORK_LOCKED, null);
          if (DBG) log("SIMRecords X ctor this=" + this);
      }
  
      @Override
      public void dispose() {
          if (DBG) log("Disposing SIMRecords this=" + this);
          //Unregister for all events
          mCi.unSetOnSmsOnSim(this);
          mParentApp.unregisterForReady(this);
          mParentApp.unregisterForLocked(this);
          mParentApp.unregisterForNetworkLocked(this);
          resetRecords();
          super.dispose();
      }
  
      @Override
      protected void finalize() {
          if (DBG) log("finalized");
      }
  
      protected void resetRecords() {
          mImsi = null;
          mMsisdn = null;
          mVoiceMailNum = null;
          mMncLength = UNINITIALIZED;
          log("setting0 mMncLength" + mMncLength);
          mIccId = null;
          mFullIccId = null;
          mCarrierNameDisplayCondition = DEFAULT_CARRIER_NAME_DISPLAY_CONDITION;
          mEfMWIS = null;
          mEfCPHS_MWI = null;
          mSpdi = null;
          mPnnHomeName = null;
          mGid1 = null;
          mGid2 = null;
          mPlmnActRecords = null;
          mOplmnActRecords = null;
          mHplmnActRecords = null;
          mFplmns = null;
          mEhplmns = null;
  
          mAdnCache.reset();
  
          log("SIMRecords: onRadioOffOrNotAvailable set 'gsm.sim.operator.numeric' to operator=null");
          log("update icc_operator_numeric=" + null);
          mTelephonyManager.setSimOperatorNumericForPhone(mParentApp.getPhoneId(), "");
          mTelephonyManager.setSimOperatorNameForPhone(mParentApp.getPhoneId(), "");
          mTelephonyManager.setSimCountryIsoForPhone(mParentApp.getPhoneId(), "");
  
          // recordsRequested is set to false indicating that the SIM
          // read requests made so far are not valid. This is set to
          // true only when fresh set of read requests are made.
          mRecordsRequested = false;
          mLockedRecordsReqReason = LOCKED_RECORDS_REQ_REASON_NONE;
          mLoaded.set(false);
      }
  
      //***** Public Methods
  
      @UnsupportedAppUsage
      @Override
      public String getMsisdnNumber() {
          return mMsisdn;
      }
  
      @Override
      public UsimServiceTable getUsimServiceTable() {
          return mUsimServiceTable;
      }
  
      @UnsupportedAppUsage
      private int getExtFromEf(int ef) {
          int ext;
          switch (ef) {
              case EF_MSISDN:
                  /* For USIM apps use EXT5. (TS 31.102 Section 4.2.37) */
                  if (mParentApp.getType() == AppType.APPTYPE_USIM) {
                      ext = EF_EXT5;
                  } else {
                      ext = EF_EXT1;
                  }
                  break;
              default:
                  ext = EF_EXT1;
          }
          return ext;
      }
  
      /**
       * Set subscriber number to SIM record
       *
       * The subscriber number is stored in EF_MSISDN (TS 51.011)
       *
       * When the operation is complete, onComplete will be sent to its handler
       *
       * @param alphaTag alpha-tagging of the dailing nubmer (up to 10 characters)
       * @param number dialing number (up to 20 digits)
       *        if the number starts with '+', then set to international TOA
       * @param onComplete
       *        onComplete.obj will be an AsyncResult
       *        ((AsyncResult)onComplete.obj).exception == null on success
       *        ((AsyncResult)onComplete.obj).exception != null on fail
       */
      @Override
      public void setMsisdnNumber(String alphaTag, String number,
              Message onComplete) {
  
          // If the SIM card is locked by PIN, we will set EF_MSISDN fail.
          // In that case, msisdn and msisdnTag should not be update.
          mNewMsisdn = number;
          mNewMsisdnTag = alphaTag;
  
          if(DBG) log("Set MSISDN: " + mNewMsisdnTag + " " + /*mNewMsisdn*/
                  Rlog.pii(LOG_TAG, mNewMsisdn));
  
          AdnRecord adn = new AdnRecord(mNewMsisdnTag, mNewMsisdn);
  
          new AdnRecordLoader(mFh).updateEF(adn, EF_MSISDN, getExtFromEf(EF_MSISDN), 1, null,
                  obtainMessage(EVENT_SET_MSISDN_DONE, onComplete));
      }
  
      @Override
      public String getMsisdnAlphaTag() {
          return mMsisdnTag;
      }
  
      @UnsupportedAppUsage
      @Override
      public String getVoiceMailNumber() {
          return mVoiceMailNum;
      }
  
      /**
       * Set voice mail number to SIM record
       *
       * The voice mail number can be stored either in EF_MBDN (TS 51.011) or
       * EF_MAILBOX_CPHS (CPHS 4.2)
       *
       * If EF_MBDN is available, store the voice mail number to EF_MBDN
       *
       * If EF_MAILBOX_CPHS is enabled, store the voice mail number to EF_CHPS
       *
       * So the voice mail number will be stored in both EFs if both are available
       *
       * Return error only if both EF_MBDN and EF_MAILBOX_CPHS fail.
       *
       * When the operation is complete, onComplete will be sent to its handler
       *
       * @param alphaTag alpha-tagging of the dailing nubmer (upto 10 characters)
       * @param voiceNumber dailing nubmer (upto 20 digits)
       *        if the number is start with '+', then set to international TOA
       * @param onComplete
       *        onComplete.obj will be an AsyncResult
       *        ((AsyncResult)onComplete.obj).exception == null on success
       *        ((AsyncResult)onComplete.obj).exception != null on fail
       */
      @Override
      public void setVoiceMailNumber(String alphaTag, String voiceNumber,
              Message onComplete) {
          if (mIsVoiceMailFixed) {
              AsyncResult.forMessage((onComplete)).exception =
                      new IccVmFixedException("Voicemail number is fixed by operator");
              onComplete.sendToTarget();
              return;
          }
  
          mNewVoiceMailNum = voiceNumber;
          mNewVoiceMailTag = alphaTag;
  
          AdnRecord adn = new AdnRecord(mNewVoiceMailTag, mNewVoiceMailNum);
  
          if (mMailboxIndex != 0 && mMailboxIndex != 0xff) {
  
              new AdnRecordLoader(mFh).updateEF(adn, EF_MBDN, EF_EXT6,
                      mMailboxIndex, null,
                      obtainMessage(EVENT_SET_MBDN_DONE, onComplete));
  
          } else if (isCphsMailboxEnabled()) {
  
              new AdnRecordLoader(mFh).updateEF(adn, EF_MAILBOX_CPHS,
                      EF_EXT1, 1, null,
                      obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE, onComplete));
  
          } else {
              AsyncResult.forMessage((onComplete)).exception =
                      new IccVmNotSupportedException("Update SIM voice mailbox error");
              onComplete.sendToTarget();
          }
      }
  
      @Override
      public String getVoiceMailAlphaTag()
      {
          return mVoiceMailTag;
      }
  
      /**
       * Sets the SIM voice message waiting indicator records
       * @param line GSM Subscriber Profile Number, one-based. Only '1' is supported
       * @param countWaiting The number of messages waiting, if known. Use
       *                     -1 to indicate that an unknown number of
       *                      messages are waiting
       */
      @Override
      public void
      setVoiceMessageWaiting(int line, int countWaiting) {
          if (line != 1) {
              // only profile 1 is supported
              return;
          }
  
          try {
              if (mEfMWIS != null) {
                  // TS 51.011 10.3.45
  
                  // lsb of byte 0 is 'voicemail' status
                  mEfMWIS[0] = (byte)((mEfMWIS[0] & 0xfe)
                                      | (countWaiting == 0 ? 0 : 1));
  
                  // byte 1 is the number of voice messages waiting
                  if (countWaiting < 0) {
                      // The spec does not define what this should be
                      // if we don't know the count
                      mEfMWIS[1] = 0;
                  } else {
                      mEfMWIS[1] = (byte) countWaiting;
                  }
  
                  mFh.updateEFLinearFixed(
                      EF_MWIS, 1, mEfMWIS, null,
                      obtainMessage (EVENT_UPDATE_DONE, EF_MWIS, 0));
              }
  
              if (mEfCPHS_MWI != null) {
                      // Refer CPHS4_2.WW6 B4.2.3
                  mEfCPHS_MWI[0] = (byte)((mEfCPHS_MWI[0] & 0xf0)
                              | (countWaiting == 0 ? 0x5 : 0xa));
                  mFh.updateEFTransparent(
                      EF_VOICE_MAIL_INDICATOR_CPHS, mEfCPHS_MWI,
                      obtainMessage (EVENT_UPDATE_DONE, EF_VOICE_MAIL_INDICATOR_CPHS));
              }
          } catch (ArrayIndexOutOfBoundsException ex) {
              logw("Error saving voice mail state to SIM. Probably malformed SIM record", ex);
          }
      }
  
      // Validate data is not null and not empty.
      private boolean validEfCfis(byte[] data) {
          if (data != null) {
              if (data[0] < 1 || data[0] > 4) {
                  // The MSP (Multiple Subscriber Profile) byte should be between
                  // 1 and 4 according to ETSI TS 131 102 v11.3.0 section 4.2.64.
                  logw("MSP byte: " + data[0] + " is not between 1 and 4", null);
              }
              // empty EF_CFIS should be considered as call forward disabled
              for (byte b : data) {
                  if (b != (byte) 0xFF) {
                      return true;
                  }
              }
          }
          return false;
      }
  
      public int getVoiceMessageCount() {
          boolean voiceMailWaiting = false;
          int countVoiceMessages = DEFAULT_VOICE_MESSAGE_COUNT;
          if (mEfMWIS != null) {
              // Use this data if the EF[MWIS] exists and
              // has been loaded
              // Refer TS 51.011 Section 10.3.45 for the content description
              voiceMailWaiting = ((mEfMWIS[0] & 0x01) != 0);
              countVoiceMessages = mEfMWIS[1] & 0xff;
  
              if (voiceMailWaiting && (countVoiceMessages == 0 || countVoiceMessages == 0xff)) {
                  // Unknown count = -1
                  countVoiceMessages = UNKNOWN_VOICE_MESSAGE_COUNT;
              }
              if (DBG) log(" VoiceMessageCount from SIM MWIS = " + countVoiceMessages);
          } else if (mEfCPHS_MWI != null) {
              // use voice mail count from CPHS
              int indicator = (int) (mEfCPHS_MWI[0] & 0xf);
  
              // Refer CPHS4_2.WW6 B4.2.3
              if (indicator == 0xA) {
                  // Unknown count = -1
                  countVoiceMessages = UNKNOWN_VOICE_MESSAGE_COUNT;
              } else if (indicator == 0x5) {
                  countVoiceMessages = 0;
              }
              if (DBG) log(" VoiceMessageCount from SIM CPHS = " + countVoiceMessages);
          }
          return countVoiceMessages;
      }
  
      /**
       * {@inheritDoc}
       */
      @Override
      public int getVoiceCallForwardingFlag() {
          return mCallForwardingStatus;
      }
  
      /**
       * {@inheritDoc}
       */
      @UnsupportedAppUsage
      @Override
      public void setVoiceCallForwardingFlag(int line, boolean enable, String dialNumber) {
  
          if (line != 1) return; // only line 1 is supported
  
          mCallForwardingStatus = enable ? CALL_FORWARDING_STATUS_ENABLED :
                  CALL_FORWARDING_STATUS_DISABLED;
  
          mRecordsEventsRegistrants.notifyResult(EVENT_CFI);
  
          try {
              if (validEfCfis(mEfCfis)) {
                  // lsb is of byte f1 is voice status
                  if (enable) {
                      mEfCfis[1] |= 1;
                  } else {
                      mEfCfis[1] &= 0xfe;
                  }
  
                  log("setVoiceCallForwardingFlag: enable=" + enable
                          + " mEfCfis=" + IccUtils.bytesToHexString(mEfCfis));
  
                  // Update dialNumber if not empty and CFU is enabled.
                  // Spec reference for EF_CFIS contents, TS 51.011 section 10.3.46.
                  if (enable && !TextUtils.isEmpty(dialNumber)) {
                      logv("EF_CFIS: updating cf number, " + Rlog.pii(LOG_TAG, dialNumber));
                      byte[] bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
                              dialNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
  
                      System.arraycopy(bcdNumber, 0, mEfCfis, CFIS_TON_NPI_OFFSET, bcdNumber.length);
  
                      mEfCfis[CFIS_BCD_NUMBER_LENGTH_OFFSET] = (byte) (bcdNumber.length);
                      mEfCfis[CFIS_ADN_CAPABILITY_ID_OFFSET] = (byte) 0xFF;
                      mEfCfis[CFIS_ADN_EXTENSION_ID_OFFSET] = (byte) 0xFF;
                  }
  
                  mFh.updateEFLinearFixed(
                          EF_CFIS, 1, mEfCfis, null,
                          obtainMessage (EVENT_UPDATE_DONE, EF_CFIS));
              } else {
                  log("setVoiceCallForwardingFlag: ignoring enable=" + enable
                          + " invalid mEfCfis=" + IccUtils.bytesToHexString(mEfCfis));
              }
  
              if (mEfCff != null) {
                  if (enable) {
                      mEfCff[0] = (byte) ((mEfCff[0] & CFF_LINE1_RESET)
                              | CFF_UNCONDITIONAL_ACTIVE);
                  } else {
                      mEfCff[0] = (byte) ((mEfCff[0] & CFF_LINE1_RESET)
                              | CFF_UNCONDITIONAL_DEACTIVE);
                  }
  
                  mFh.updateEFTransparent(
                          EF_CFF_CPHS, mEfCff,
                          obtainMessage (EVENT_UPDATE_DONE, EF_CFF_CPHS));
              }
          } catch (ArrayIndexOutOfBoundsException ex) {
              logw("Error saving call forwarding flag to SIM. "
                              + "Probably malformed SIM record", ex);
  
          }
      }
  
      /**
       * Called by STK Service when REFRESH is received.
       * @param fileChanged indicates whether any files changed
       * @param fileList if non-null, a list of EF files that changed
       */
      @Override
      public void onRefresh(boolean fileChanged, int[] fileList) {
          if (fileChanged) {
              // A future optimization would be to inspect fileList and
              // only reload those files that we care about.  For now,
              // just re-fetch all SIM records that we cache.
              fetchSimRecords();
          }
      }
  
      /**
       * {@inheritDoc}
       */
      @UnsupportedAppUsage
      @Override
      public String getOperatorNumeric() {
          String imsi = getIMSI();
          if (imsi == null) {
              log("getOperatorNumeric: IMSI == null");
              return null;
          }
          if (mMncLength == UNINITIALIZED || mMncLength == UNKNOWN) {
              log("getSIMOperatorNumeric: bad mncLength");
              return null;
          }
  
          // Length = length of MCC + length of MNC
          // length of mcc = 3 (TS 23.003 Section 2.2)
          if (imsi.length() >= 3 + mMncLength) {
              return imsi.substring(0, 3 + mMncLength);
          } else {
              return null;
          }
      }
  
      // ***** Overridden from Handler
      @Override
      public void handleMessage(Message msg) {
          AsyncResult ar;
          AdnRecord adn;
  
          byte data[];
  
          boolean isRecordLoadResponse = false;
  
          if (mDestroyed.get()) {
              loge("Received message " + msg + "[" + msg.what + "] " +
                      " while being destroyed. Ignoring.");
              return;
          }
  
          try {
              switch (msg.what) {
                  case EVENT_APP_READY:
                      onReady();
                      break;
  
                  case EVENT_APP_LOCKED:
                  case EVENT_APP_NETWORK_LOCKED:
                      onLocked(msg.what);
                      break;
  
                  /* IO events */
                  case EVENT_GET_IMSI_DONE:
                      isRecordLoadResponse = true;
                      ar = (AsyncResult) msg.obj;
  
                      if (ar.exception != null) {
                          loge("Exception querying IMSI, Exception:" + ar.exception);
                          break;
                      }
  
                      setImsi((String) ar.result);
                      break;
  
                  case EVENT_GET_MBI_DONE:
                      boolean isValidMbdn;
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
  
                      isValidMbdn = false;
                      if (ar.exception == null) {
                          // Refer TS 51.011 Section 10.3.44 for content details
                          log("EF_MBI: " + IccUtils.bytesToHexString(data));
  
                          // Voice mail record number stored first
                          mMailboxIndex = data[0] & 0xff;
  
                          // check if dailing numbe id valid
                          if (mMailboxIndex != 0 && mMailboxIndex != 0xff) {
                              log("Got valid mailbox number for MBDN");
                              isValidMbdn = true;
                          }
                      }
  
                      // one more record to load
                      mRecordsToLoad += 1;
  
                      if (isValidMbdn) {
                          // Note: MBDN was not included in NUM_OF_SIM_RECORDS_LOADED
                          new AdnRecordLoader(mFh).loadFromEF(EF_MBDN, EF_EXT6,
                                  mMailboxIndex, obtainMessage(EVENT_GET_MBDN_DONE));
                      } else {
                          // If this EF not present, try mailbox as in CPHS standard
                          // CPHS (CPHS4_2.WW6) is a european standard.
                          new AdnRecordLoader(mFh).loadFromEF(EF_MAILBOX_CPHS,
                                  EF_EXT1, 1,
                                  obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
                      }
  
                      break;
                  case EVENT_GET_CPHS_MAILBOX_DONE:
                  case EVENT_GET_MBDN_DONE:
                      //Resetting the voice mail number and voice mail tag to null
                      //as these should be updated from the data read from EF_MBDN.
                      //If they are not reset, incase of invalid data/exception these
                      //variables are retaining their previous values and are
                      //causing invalid voice mailbox info display to user.
                      mVoiceMailNum = null;
                      mVoiceMailTag = null;
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
  
                      if (ar.exception != null) {
  
                          log("Invalid or missing EF"
                                  + ((msg.what == EVENT_GET_CPHS_MAILBOX_DONE)
                                      ? "[MAILBOX]" : "[MBDN]"));
  
                          // Bug #645770 fall back to CPHS
                          // FIXME should use SST to decide
  
                          if (msg.what == EVENT_GET_MBDN_DONE) {
                              //load CPHS on fail...
                              // FIXME right now, only load line1's CPHS voice mail entry
  
                              mRecordsToLoad += 1;
                              new AdnRecordLoader(mFh).loadFromEF(
                                      EF_MAILBOX_CPHS, EF_EXT1, 1,
                                      obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
                          }
                          break;
                      }
  
                      adn = (AdnRecord) ar.result;
  
                      log("VM: " + adn
                              + ((msg.what == EVENT_GET_CPHS_MAILBOX_DONE)
                                  ? " EF[MAILBOX]" : " EF[MBDN]"));
  
                      if (adn.isEmpty() && msg.what == EVENT_GET_MBDN_DONE) {
                          // Bug #645770 fall back to CPHS
                          // FIXME should use SST to decide
                          // FIXME right now, only load line1's CPHS voice mail entry
                          mRecordsToLoad += 1;
                          new AdnRecordLoader(mFh).loadFromEF(
                                  EF_MAILBOX_CPHS, EF_EXT1, 1,
                                  obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
  
                          break;
                      }
  
                      mVoiceMailNum = adn.getNumber();
                      mVoiceMailTag = adn.getAlphaTag();
                      break;
  
                  case EVENT_GET_MSISDN_DONE:
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
  
                      if (ar.exception != null) {
                          log("Invalid or missing EF[MSISDN]");
                          break;
                      }
  
                      adn = (AdnRecord) ar.result;
  
                      mMsisdn = adn.getNumber();
                      mMsisdnTag = adn.getAlphaTag();
  
                      log("MSISDN: " + /*mMsisdn*/ Rlog.pii(LOG_TAG, mMsisdn));
                      break;
  
                  case EVENT_SET_MSISDN_DONE:
                      isRecordLoadResponse = false;
                      ar = (AsyncResult) msg.obj;
  
                      if (ar.exception == null) {
                          mMsisdn = mNewMsisdn;
                          mMsisdnTag = mNewMsisdnTag;
                          log("Success to update EF[MSISDN]");
                      }
  
                      if (ar.userObj != null) {
                          AsyncResult.forMessage(((Message) ar.userObj)).exception = ar.exception;
                          ((Message) ar.userObj).sendToTarget();
                      }
                      break;
  
                  case EVENT_GET_MWIS_DONE:
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
  
                      if (DBG) log("EF_MWIS : " + IccUtils.bytesToHexString(data));
  
                      if (ar.exception != null) {
                          if (DBG) log("EVENT_GET_MWIS_DONE exception = " + ar.exception);
                          break;
                      }
  
                      if ((data[0] & 0xff) == 0xff) {
                          if (DBG) log("SIMRecords: Uninitialized record MWIS");
                          break;
                      }
  
                      mEfMWIS = data;
                      break;
  
                  case EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE:
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
  
                      if (DBG) log("EF_CPHS_MWI: " + IccUtils.bytesToHexString(data));
  
                      if (ar.exception != null) {
                          if (DBG) {
                              log("EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE exception = "
                                      + ar.exception);
                          }
                          break;
                      }
  
                      mEfCPHS_MWI = data;
                      break;
  
                  case EVENT_GET_ICCID_DONE:
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
  
                      if (ar.exception != null) {
                          break;
                      }
  
                      mIccId = IccUtils.bcdToString(data, 0, data.length);
                      mFullIccId = IccUtils.bchToString(data, 0, data.length);
  
                      log("iccid: " + SubscriptionInfo.givePrintableIccid(mFullIccId));
                      break;
  
                  case EVENT_GET_AD_DONE:
                      isRecordLoadResponse = true;
                      mMncLength = UNKNOWN;
                      try {
                          if (!mCarrierTestOverride.isInTestMode()) {
                              ar = (AsyncResult) msg.obj;
                              data = (byte[]) ar.result;
  
                              if (ar.exception != null) {
                                  break;
                              }
  
                              log("EF_AD: " + IccUtils.bytesToHexString(data));
  
                              if (data.length < 3) {
                                  log("Corrupt AD data on SIM");
                                  break;
                              }
  
                              if (data.length == 3) {
                                  log("MNC length not present in EF_AD");
                                  break;
                              }
  
                              int len = data[3] & 0xf;
                              if (len == 2 || len == 3) {
                                  mMncLength = len;
                              } else {
                                  log("Received invalid or unset MNC Length=" + len);
                              }
                          }
                      } finally {
                          updateOperatorPlmn();
                      }
                      break;
  
                  case EVENT_GET_SPN_DONE:
                      isRecordLoadResponse = true;
                      ar = (AsyncResult) msg.obj;
                      getSpnFsm(false, ar);
                      break;
  
                  case EVENT_GET_CFF_DONE:
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
  
                      if (ar.exception != null) {
                          mEfCff = null;
                      } else {
                          log("EF_CFF_CPHS: " + IccUtils.bytesToHexString(data));
                          mEfCff = data;
                      }
  
                      break;
  
                  case EVENT_GET_SPDI_DONE:
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
  
                      if (ar.exception != null) {
                          break;
                      }
  
                      parseEfSpdi(data);
                      break;
  
                  case EVENT_UPDATE_DONE:
                      ar = (AsyncResult) msg.obj;
                      if (ar.exception != null) {
                          logw("update failed. ", ar.exception);
                      }
                      break;
  
                  case EVENT_GET_PNN_DONE:
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
  
                      if (ar.exception != null) {
                          break;
                      }
  
                      SimTlv tlv = new SimTlv(data, 0, data.length);
  
                      for (; tlv.isValidObject(); tlv.nextObject()) {
                          if (tlv.getTag() == TAG_FULL_NETWORK_NAME) {
                              mPnnHomeName = IccUtils.networkNameToString(
                                      tlv.getData(), 0, tlv.getData().length);
                              log("PNN: " + mPnnHomeName);
                              break;
                          }
                      }
                      break;
  
                  case EVENT_GET_ALL_SMS_DONE:
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
                      if (ar.exception != null) {
                          break;
                      }
  
                      handleSmses((ArrayList<byte []>) ar.result);
                      break;
  
                  case EVENT_MARK_SMS_READ_DONE:
                      Rlog.i("ENF", "marked read: sms " + msg.arg1);
                      break;
  
  
                  case EVENT_SMS_ON_SIM:
                      isRecordLoadResponse = false;
  
                      ar = (AsyncResult) msg.obj;
  
                      Integer index = (Integer) ar.result;
  
                      if (ar.exception != null || index == null) {
                          loge("Error on SMS_ON_SIM with exp "
                                  + ar.exception + " index " + index);
                      } else {
                          log("READ EF_SMS RECORD index=" + index);
                          mFh.loadEFLinearFixed(EF_SMS, index, obtainMessage(EVENT_GET_SMS_DONE));
                      }
                      break;
  
                  case EVENT_GET_SMS_DONE:
                      isRecordLoadResponse = false;
                      ar = (AsyncResult) msg.obj;
                      if (ar.exception == null) {
                          handleSms((byte[]) ar.result);
                      } else {
                          loge("Error on GET_SMS with exp " + ar.exception);
                      }
                      break;
                  case EVENT_GET_SST_DONE:
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
  
                      if (ar.exception != null) {
                          break;
                      }
  
                      mUsimServiceTable = new UsimServiceTable(data);
                      if (DBG) log("SST: " + mUsimServiceTable);
                      break;
  
                  case EVENT_GET_INFO_CPHS_DONE:
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
  
                      if (ar.exception != null) {
                          break;
                      }
  
                      mCphsInfo = (byte[]) ar.result;
  
                      if (DBG) log("iCPHS: " + IccUtils.bytesToHexString(mCphsInfo));
                      break;
  
                  case EVENT_SET_MBDN_DONE:
                      isRecordLoadResponse = false;
                      ar = (AsyncResult) msg.obj;
  
                      if (DBG) log("EVENT_SET_MBDN_DONE ex:" + ar.exception);
                      if (ar.exception == null) {
                          mVoiceMailNum = mNewVoiceMailNum;
                          mVoiceMailTag = mNewVoiceMailTag;
                      }
  
                      if (isCphsMailboxEnabled()) {
                          adn = new AdnRecord(mVoiceMailTag, mVoiceMailNum);
                          Message onCphsCompleted = (Message) ar.userObj;
  
                          /* write to cphs mailbox whenever it is available but
                          * we only need notify caller once if both updating are
                          * successful.
                          *
                          * so if set_mbdn successful, notify caller here and set
                          * onCphsCompleted to null
                          */
                          if (ar.exception == null && ar.userObj != null) {
                              AsyncResult.forMessage(((Message) ar.userObj)).exception = null;
                              ((Message) ar.userObj).sendToTarget();
  
                              if (DBG) log("Callback with MBDN successful.");
  
                              onCphsCompleted = null;
                          }
  
                          new AdnRecordLoader(mFh)
                                  .updateEF(adn, EF_MAILBOX_CPHS, EF_EXT1, 1, null,
                                  obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE,
                                          onCphsCompleted));
                      } else {
                          if (ar.userObj != null) {
                              CarrierConfigManager configManager = (CarrierConfigManager)
                                      mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
                              if (ar.exception != null && configManager != null) {
                                  PersistableBundle b = configManager.getConfigForSubId(
                                          SubscriptionController.getInstance().getSubIdUsingPhoneId(
                                                  mParentApp.getPhoneId()));
                                  if (b != null && b.getBoolean(
                                          CarrierConfigManager.KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL)) {
                                      // GsmCdmaPhone will store vm number on device
                                      // when IccVmNotSupportedException occurred
                                      AsyncResult.forMessage(((Message) ar.userObj)).exception =
                                              new IccVmNotSupportedException(
                                                      "Update SIM voice mailbox error");
                                  } else {
                                      AsyncResult.forMessage(((Message) ar.userObj))
                                              .exception = ar.exception;
                                  }
                              } else {
                                  AsyncResult.forMessage(((Message) ar.userObj))
                                      .exception = ar.exception;
                              }
                              ((Message) ar.userObj).sendToTarget();
                          }
                      }
                      break;
                  case EVENT_SET_CPHS_MAILBOX_DONE:
                      isRecordLoadResponse = false;
                      ar = (AsyncResult) msg.obj;
                      if (ar.exception == null) {
                          mVoiceMailNum = mNewVoiceMailNum;
                          mVoiceMailTag = mNewVoiceMailTag;
                      } else {
                          if (DBG) log("Set CPHS MailBox with exception: " + ar.exception);
                      }
                      if (ar.userObj != null) {
                          if (DBG) log("Callback with CPHS MB successful.");
                          AsyncResult.forMessage(((Message) ar.userObj)).exception
                                  = ar.exception;
                          ((Message) ar.userObj).sendToTarget();
                      }
                      break;
                  case EVENT_GET_CFIS_DONE:
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
  
                      if (ar.exception != null) {
                          mEfCfis = null;
                      } else {
                          log("EF_CFIS: " + IccUtils.bytesToHexString(data));
                          mEfCfis = data;
                      }
  
                      break;
  
                  case EVENT_GET_CSP_CPHS_DONE:
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
  
                      if (ar.exception != null) {
                          loge("Exception in fetching EF_CSP data " + ar.exception);
                          break;
                      }
  
                      data = (byte[]) ar.result;
  
                      log("EF_CSP: " + IccUtils.bytesToHexString(data));
                      handleEfCspData(data);
                      break;
  
                  case EVENT_GET_GID1_DONE:
                      isRecordLoadResponse = true;
  
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
  
                      if (ar.exception != null) {
                          loge("Exception in get GID1 " + ar.exception);
                          mGid1 = null;
                          break;
                      }
  
                      mGid1 = IccUtils.bytesToHexString(data);
  
                      log("GID1: " + mGid1);
  
                      break;
  
                  case EVENT_GET_GID2_DONE:
                      isRecordLoadResponse = true;
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
  
                      if (ar.exception != null) {
                          loge("Exception in get GID2 " + ar.exception);
                          mGid2 = null;
                          break;
                      }
  
                      mGid2 = IccUtils.bytesToHexString(data);
  
                      log("GID2: " + mGid2);
  
                      break;
  
                  case EVENT_GET_PLMN_W_ACT_DONE:
                      isRecordLoadResponse = true;
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
  
                      if (ar.exception != null || data == null) {
                          loge("Failed getting User PLMN with Access Tech Records: " + ar.exception);
                          break;
                      } else {
                          log("Received a PlmnActRecord, raw=" + IccUtils.bytesToHexString(data));
                          mPlmnActRecords = PlmnActRecord.getRecords(data);
                          if (VDBG) log("PlmnActRecords=" + Arrays.toString(mPlmnActRecords));
                      }
                      break;
  
                  case EVENT_GET_OPLMN_W_ACT_DONE:
                      isRecordLoadResponse = true;
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
  
                      if (ar.exception != null || data == null) {
                          loge("Failed getting Operator PLMN with Access Tech Records: "
                                  + ar.exception);
                          break;
                      } else {
                          log("Received a PlmnActRecord, raw=" + IccUtils.bytesToHexString(data));
                          mOplmnActRecords = PlmnActRecord.getRecords(data);
                          if (VDBG) log("OplmnActRecord[]=" + Arrays.toString(mOplmnActRecords));
                      }
                      break;
  
                  case EVENT_GET_HPLMN_W_ACT_DONE:
                      isRecordLoadResponse = true;
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
  
                      if (ar.exception != null || data == null) {
                          loge("Failed getting Home PLMN with Access Tech Records: " + ar.exception);
                          break;
                      } else {
                          log("Received a PlmnActRecord, raw=" + IccUtils.bytesToHexString(data));
                          mHplmnActRecords = PlmnActRecord.getRecords(data);
                          log("HplmnActRecord[]=" + Arrays.toString(mHplmnActRecords));
                      }
                      break;
  
                  case EVENT_GET_EHPLMN_DONE:
                      isRecordLoadResponse = true;
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
                      if (ar.exception != null || data == null) {
                          loge("Failed getting Equivalent Home PLMNs: " + ar.exception);
                          break;
                      } else {
                          mEhplmns = parseBcdPlmnList(data, "Equivalent Home");
                      }
                      break;
  
                  case EVENT_GET_FPLMN_DONE:
                      isRecordLoadResponse = true;
                      ar = (AsyncResult) msg.obj;
                      data = (byte[]) ar.result;
                      if (ar.exception != null || data == null) {
                          loge("Failed getting Forbidden PLMNs: " + ar.exception);
                          break;
                      } else {
                          mFplmns = parseBcdPlmnList(data, "Forbidden");
                      }
                      if (msg.arg1 == HANDLER_ACTION_SEND_RESPONSE) {
                          if (VDBG) logv("getForbiddenPlmns(): send async response");
                          isRecordLoadResponse = false;
                          Message response = retrievePendingResponseMessage(msg.arg2);
                          if (response != null) {
                              AsyncResult.forMessage(
                                      response, Arrays.copyOf(mFplmns, mFplmns.length), null);
                              response.sendToTarget();
                          } else {
                              loge("Failed to retrieve a response message for FPLMN");
                              break;
                          }
                      }
                      break;
  
                  default:
                      super.handleMessage(msg);   // IccRecords handles generic record load responses
              }
          } catch (RuntimeException exc) {
              // I don't want these exceptions to be fatal
              logw("Exception parsing SIM record", exc);
          } finally {
              // Count up record load responses even if they are fails
              if (isRecordLoadResponse) {
                  onRecordLoaded();
              }
          }
      }
  
      private class EfPlLoaded implements IccRecordLoaded {
          public String getEfName() {
              return "EF_PL";
          }
  
          public void onRecordLoaded(AsyncResult ar) {
              mEfPl = (byte[]) ar.result;
              if (DBG) log("EF_PL=" + IccUtils.bytesToHexString(mEfPl));
          }
      }
  
      private class EfUsimLiLoaded implements IccRecordLoaded {
          public String getEfName() {
              return "EF_LI";
          }
  
          public void onRecordLoaded(AsyncResult ar) {
              mEfLi = (byte[]) ar.result;
              if (DBG) log("EF_LI=" + IccUtils.bytesToHexString(mEfLi));
          }
      }
  
      @Override
      protected void handleFileUpdate(int efid) {
          switch(efid) {
              case EF_MBDN:
                  mRecordsToLoad++;
                  new AdnRecordLoader(mFh).loadFromEF(EF_MBDN, EF_EXT6,
                          mMailboxIndex, obtainMessage(EVENT_GET_MBDN_DONE));
                  break;
              case EF_MAILBOX_CPHS:
                  mRecordsToLoad++;
                  new AdnRecordLoader(mFh).loadFromEF(EF_MAILBOX_CPHS, EF_EXT1,
                          1, obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
                  break;
              case EF_CSP_CPHS:
                  mRecordsToLoad++;
                  log("[CSP] SIM Refresh for EF_CSP_CPHS");
                  mFh.loadEFTransparent(EF_CSP_CPHS,
                          obtainMessage(EVENT_GET_CSP_CPHS_DONE));
                  break;
              case EF_FDN:
                  if (DBG) log("SIM Refresh called for EF_FDN");
                  mParentApp.queryFdn();
                  mAdnCache.reset();
                  break;
              case EF_MSISDN:
                  mRecordsToLoad++;
                  log("SIM Refresh called for EF_MSISDN");
                  new AdnRecordLoader(mFh).loadFromEF(EF_MSISDN, getExtFromEf(EF_MSISDN), 1,
                          obtainMessage(EVENT_GET_MSISDN_DONE));
                  break;
              case EF_CFIS:
              case EF_CFF_CPHS:
                  log("SIM Refresh called for EF_CFIS or EF_CFF_CPHS");
                  loadCallForwardingRecords();
                  break;
              default:
                  // For now, fetch all records if this is not a
                  // voicemail number.
                  // TODO: Handle other cases, instead of fetching all.
                  mAdnCache.reset();
                  fetchSimRecords();
                  break;
          }
      }
  
      /**
       * Dispatch 3GPP format message to registrant ({@code GsmCdmaPhone}) to pass to the 3GPP SMS
       * dispatcher for delivery.
       */
      private int dispatchGsmMessage(SmsMessage message) {
          mNewSmsRegistrants.notifyResult(message);
          return 0;
      }
  
      private void handleSms(byte[] ba) {
          if (ba[0] != 0)
              Rlog.d("ENF", "status : " + ba[0]);
  
          // 3GPP TS 51.011 v5.0.0 (20011-12)  10.5.3
          // 3 == "received by MS from network; message to be read"
          if (ba[0] == 3) {
              int n = ba.length;
  
              // Note: Data may include trailing FF's.  That's OK; message
              // should still parse correctly.
              byte[] pdu = new byte[n - 1];
              System.arraycopy(ba, 1, pdu, 0, n - 1);
              SmsMessage message = SmsMessage.createFromPdu(pdu, SmsConstants.FORMAT_3GPP);
  
              dispatchGsmMessage(message);
          }
      }
  
  
      private void handleSmses(ArrayList<byte[]> messages) {
          int count = messages.size();
  
          for (int i = 0; i < count; i++) {
              byte[] ba = messages.get(i);
  
              if (ba[0] != 0)
                  Rlog.i("ENF", "status " + i + ": " + ba[0]);
  
              // 3GPP TS 51.011 v5.0.0 (20011-12)  10.5.3
              // 3 == "received by MS from network; message to be read"
  
              if (ba[0] == 3) {
                  int n = ba.length;
  
                  // Note: Data may include trailing FF's.  That's OK; message
                  // should still parse correctly.
                  byte[] pdu = new byte[n - 1];
                  System.arraycopy(ba, 1, pdu, 0, n - 1);
                  SmsMessage message = SmsMessage.createFromPdu(pdu, SmsConstants.FORMAT_3GPP);
  
                  dispatchGsmMessage(message);
  
                  // 3GPP TS 51.011 v5.0.0 (20011-12)  10.5.3
                  // 1 == "received by MS from network; message read"
  
                  ba[0] = 1;
  
                  if (false) { // FIXME: writing seems to crash RdoServD
                      mFh.updateEFLinearFixed(EF_SMS,
                              i, ba, null, obtainMessage(EVENT_MARK_SMS_READ_DONE, i));
                  }
              }
          }
      }
  
      @Override
      protected void onRecordLoaded() {
          // One record loaded successfully or failed, In either case
          // we need to update the recordsToLoad count
          mRecordsToLoad -= 1;
          if (DBG) log("onRecordLoaded " + mRecordsToLoad + " requested: " + mRecordsRequested);
  
          if (getRecordsLoaded()) {
              onAllRecordsLoaded();
          } else if (getLockedRecordsLoaded() || getNetworkLockedRecordsLoaded()) {
              onLockedAllRecordsLoaded();
          } else if (mRecordsToLoad < 0) {
              loge("recordsToLoad <0, programmer error suspected");
              mRecordsToLoad = 0;
          }
      }
  
      private void setVoiceCallForwardingFlagFromSimRecords() {
          if (validEfCfis(mEfCfis)) {
              // Refer TS 51.011 Section 10.3.46 for the content description
              mCallForwardingStatus = (mEfCfis[1] & 0x01);
              log("EF_CFIS: callForwardingEnabled=" + mCallForwardingStatus);
          } else if (mEfCff != null) {
              mCallForwardingStatus =
                      ((mEfCff[0] & CFF_LINE1_MASK) == CFF_UNCONDITIONAL_ACTIVE) ?
                              CALL_FORWARDING_STATUS_ENABLED : CALL_FORWARDING_STATUS_DISABLED;
              log("EF_CFF: callForwardingEnabled=" + mCallForwardingStatus);
          } else {
              mCallForwardingStatus = CALL_FORWARDING_STATUS_UNKNOWN;
              log("EF_CFIS and EF_CFF not valid. callForwardingEnabled=" + mCallForwardingStatus);
          }
      }
  
      private void setSimLanguageFromEF() {
          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");
          }
      }
  
      private void onLockedAllRecordsLoaded() {
          setSimLanguageFromEF();
          setVoiceCallForwardingFlagFromSimRecords();
          if (mLockedRecordsReqReason == LOCKED_RECORDS_REQ_REASON_LOCKED) {
              mLockedRecordsLoadedRegistrants.notifyRegistrants(new AsyncResult(null, null, null));
          } else if (mLockedRecordsReqReason == LOCKED_RECORDS_REQ_REASON_NETWORK_LOCKED) {
              mNetworkLockedRecordsLoadedRegistrants.notifyRegistrants(
                      new AsyncResult(null, null, null));
          } else {
              loge("onLockedAllRecordsLoaded: unexpected mLockedRecordsReqReason "
                      + mLockedRecordsReqReason);
          }
      }
  
      @Override
      protected void onAllRecordsLoaded() {
          if (DBG) log("record load complete");
  
          setSimLanguageFromEF();
          setVoiceCallForwardingFlagFromSimRecords();
  
          // 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);
          } else {
              log("onAllRecordsLoaded empty 'gsm.sim.operator.numeric' skipping");
          }
  
          String imsi = getIMSI();
  
          if (!TextUtils.isEmpty(imsi) && imsi.length() >= 3) {
              log("onAllRecordsLoaded set mcc imsi" + (VDBG ? ("=" + imsi) : ""));
              mTelephonyManager.setSimCountryIsoForPhone(
                      mParentApp.getPhoneId(), MccTable.countryCodeForMcc(imsi.substring(0, 3)));
          } else {
              log("onAllRecordsLoaded empty imsi skipping setting mcc");
          }
  
          setVoiceMailByCountry(operator);
          mLoaded.set(true);
          mRecordsLoadedRegistrants.notifyRegistrants(new AsyncResult(null, null, null));
      }
  
      //***** Private methods
  
      private void setVoiceMailByCountry (String spn) {
          if (mVmConfig.containsCarrier(spn)) {
              mIsVoiceMailFixed = true;
              mVoiceMailNum = mVmConfig.getVoiceMailNumber(spn);
              mVoiceMailTag = mVmConfig.getVoiceMailTag(spn);
          }
      }
  
      /**
       * String[] of forbidden PLMNs will be sent to the Message's handler
       * in the result field of an AsyncResult in the response.obj.
       */
      public void getForbiddenPlmns(Message response) {
          int key = storePendingResponseMessage(response);
          mFh.loadEFTransparent(EF_FPLMN, obtainMessage(
                      EVENT_GET_FPLMN_DONE, HANDLER_ACTION_SEND_RESPONSE, key));
      }
  
      @Override
      public void onReady() {
          fetchSimRecords();
      }
  
      private void onLocked(int msg) {
          if (DBG) log("only fetch EF_LI, EF_PL and EF_ICCID in locked state");
          mLockedRecordsReqReason = msg == EVENT_APP_LOCKED ? LOCKED_RECORDS_REQ_REASON_LOCKED :
                  LOCKED_RECORDS_REQ_REASON_NETWORK_LOCKED;
  
          loadEfLiAndEfPl();
  
          mFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE));
          mRecordsToLoad++;
      }
  
      private void loadEfLiAndEfPl() {
          if (mParentApp.getType() == AppType.APPTYPE_USIM) {
              mFh.loadEFTransparent(EF_LI,
                      obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfUsimLiLoaded()));
              mRecordsToLoad++;
  
              mFh.loadEFTransparent(EF_PL,
                      obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfPlLoaded()));
              mRecordsToLoad++;
          }
      }
  
      private void loadCallForwardingRecords() {
          mRecordsRequested = true;
          mFh.loadEFLinearFixed(EF_CFIS, 1, obtainMessage(EVENT_GET_CFIS_DONE));
          mRecordsToLoad++;
          mFh.loadEFTransparent(EF_CFF_CPHS, obtainMessage(EVENT_GET_CFF_DONE));
          mRecordsToLoad++;
      }
  
      @UnsupportedAppUsage
      protected void fetchSimRecords() {
          mRecordsRequested = true;
  
          if (DBG) log("fetchSimRecords " + mRecordsToLoad);
  
          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);
  
          mFh.loadEFTransparent(EF_SPDI, obtainMessage(EVENT_GET_SPDI_DONE));
          mRecordsToLoad++;
  
          mFh.loadEFLinearFixed(EF_PNN, 1, obtainMessage(EVENT_GET_PNN_DONE));
          mRecordsToLoad++;
  
          mFh.loadEFTransparent(EF_SST, obtainMessage(EVENT_GET_SST_DONE));
          mRecordsToLoad++;
  
          mFh.loadEFTransparent(EF_INFO_CPHS, obtainMessage(EVENT_GET_INFO_CPHS_DONE));
          mRecordsToLoad++;
  
          mFh.loadEFTransparent(EF_CSP_CPHS,obtainMessage(EVENT_GET_CSP_CPHS_DONE));
          mRecordsToLoad++;
  
          mFh.loadEFTransparent(EF_GID1, obtainMessage(EVENT_GET_GID1_DONE));
          mRecordsToLoad++;
  
          mFh.loadEFTransparent(EF_GID2, obtainMessage(EVENT_GET_GID2_DONE));
          mRecordsToLoad++;
  
          mFh.loadEFTransparent(EF_PLMN_W_ACT, obtainMessage(EVENT_GET_PLMN_W_ACT_DONE));
          mRecordsToLoad++;
  
          mFh.loadEFTransparent(EF_OPLMN_W_ACT, obtainMessage(EVENT_GET_OPLMN_W_ACT_DONE));
          mRecordsToLoad++;
  
          mFh.loadEFTransparent(EF_HPLMN_W_ACT, obtainMessage(EVENT_GET_HPLMN_W_ACT_DONE));
          mRecordsToLoad++;
  
          mFh.loadEFTransparent(EF_EHPLMN, obtainMessage(EVENT_GET_EHPLMN_DONE));
          mRecordsToLoad++;
  
          mFh.loadEFTransparent(EF_FPLMN, obtainMessage(
                      EVENT_GET_FPLMN_DONE, HANDLER_ACTION_NONE, -1));
          mRecordsToLoad++;
  
          loadEfLiAndEfPl();
  
          // XXX should seek instead of examining them all
          if (false) { // XXX
              mFh.loadEFLinearFixedAll(EF_SMS, obtainMessage(EVENT_GET_ALL_SMS_DONE));
              mRecordsToLoad++;
          }
  
          if (CRASH_RIL) {
              String sms = "0107912160130310f20404d0110041007030208054832b0120"
                           + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
                           + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
                           + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
                           + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
                           + "ffffffffffffffffffffffffffffff";
              byte[] ba = IccUtils.hexStringToBytes(sms);
  
              mFh.updateEFLinearFixed(EF_SMS, 1, ba, null,
                              obtainMessage(EVENT_MARK_SMS_READ_DONE, 1));
          }
          if (DBG) log("fetchSimRecords " + mRecordsToLoad + " requested: " + mRecordsRequested);
      }
  
      @Override
      @CarrierNameDisplayConditionBitmask
      public int getCarrierNameDisplayCondition() {
          return mCarrierNameDisplayCondition;
      }
  
      /**
       * States of Get SPN Finite State Machine which only used by getSpnFsm()
       */
      private enum GetSpnFsmState {
          IDLE,               // No initialized
          @UnsupportedAppUsage
          INIT,               // Start FSM
          @UnsupportedAppUsage
          READ_SPN_3GPP,      // Load EF_SPN firstly
          @UnsupportedAppUsage
          READ_SPN_CPHS,      // Load EF_SPN_CPHS secondly
          @UnsupportedAppUsage
          READ_SPN_SHORT_CPHS // Load EF_SPN_SHORT_CPHS last
      }
  
      /**
       * Finite State Machine to load Service Provider Name , which can be stored
       * in either EF_SPN (3GPP), EF_SPN_CPHS, or EF_SPN_SHORT_CPHS (CPHS4.2)
       *
       * After starting, FSM will search SPN EFs in order and stop after finding
       * the first valid SPN
       *
       * If the FSM gets restart while waiting for one of
       * SPN EFs results (i.e. a SIM refresh occurs after issuing
       * read EF_CPHS_SPN), it will re-initialize only after
       * receiving and discarding the unfinished SPN EF result.
       *
       * @param start set true only for initialize loading
       * @param ar the AsyncResult from loadEFTransparent
       *        ar.exception holds exception in error
       *        ar.result is byte[] for data in success
       */
      @UnsupportedAppUsage
      private void getSpnFsm(boolean start, AsyncResult ar) {
          byte[] data;
  
          if (start) {
              // Check previous state to see if there is outstanding
              // SPN read
              if (mSpnState == GetSpnFsmState.READ_SPN_3GPP
                      || mSpnState == GetSpnFsmState.READ_SPN_CPHS
                      || mSpnState == GetSpnFsmState.READ_SPN_SHORT_CPHS
                      || mSpnState == GetSpnFsmState.INIT) {
                  // Set INIT then return so the INIT code
                  // will run when the outstanding read done.
                  mSpnState = GetSpnFsmState.INIT;
                  return;
              } else {
                  mSpnState = GetSpnFsmState.INIT;
              }
          }
  
          switch(mSpnState){
              case INIT:
                  setServiceProviderName(null);
  
                  mFh.loadEFTransparent(EF_SPN,
                          obtainMessage(EVENT_GET_SPN_DONE));
                  mRecordsToLoad++;
  
                  mSpnState = GetSpnFsmState.READ_SPN_3GPP;
                  break;
              case READ_SPN_3GPP:
                  if (ar != null && ar.exception == null) {
                      data = (byte[]) ar.result;
  
                      // Reference: 3GPP TS 31.102 section 4.2.12 EF_SPN
                      // The first byte is display condition.
                      mCarrierNameDisplayCondition =
                              convertSpnDisplayConditionToBitmask(data[0] & 0xff);
  
                      setServiceProviderName(IccUtils.adnStringFieldToString(
                                  data, 1, data.length - 1));
                      // for card double-check and brand override
                      // we have to do this:
                      final String spn = getServiceProviderName();
  
                      if (spn == null || spn.length() == 0) {
                          mSpnState = GetSpnFsmState.READ_SPN_CPHS;
                      } else {
                          if (DBG) log("Load EF_SPN: " + spn
                                  + " carrierNameDisplayCondition: " + mCarrierNameDisplayCondition);
                          mTelephonyManager.setSimOperatorNameForPhone(
                                  mParentApp.getPhoneId(), spn);
  
                          mSpnState = GetSpnFsmState.IDLE;
                      }
                  } else {
                      mSpnState = GetSpnFsmState.READ_SPN_CPHS;
                  }
  
                  if (mSpnState == GetSpnFsmState.READ_SPN_CPHS) {
                      mFh.loadEFTransparent( EF_SPN_CPHS,
                              obtainMessage(EVENT_GET_SPN_DONE));
                      mRecordsToLoad++;
  
                      mCarrierNameDisplayCondition = DEFAULT_CARRIER_NAME_DISPLAY_CONDITION;
                  }
                  break;
              case READ_SPN_CPHS:
                  if (ar != null && ar.exception == null) {
                      data = (byte[]) ar.result;
  
                      setServiceProviderName(IccUtils.adnStringFieldToString(
                                  data, 0, data.length));
                      // for card double-check and brand override
                      // we have to do this:
                      final String spn = getServiceProviderName();
  
                      if (spn == null || spn.length() == 0) {
                          mSpnState = GetSpnFsmState.READ_SPN_SHORT_CPHS;
                      } else {
                          // Display CPHS Operator Name only when not roaming
                          mCarrierNameDisplayCondition = 0;
  
                          if (DBG) log("Load EF_SPN_CPHS: " + spn);
                          mTelephonyManager.setSimOperatorNameForPhone(
                                  mParentApp.getPhoneId(), spn);
  
                          mSpnState = GetSpnFsmState.IDLE;
                      }
                  } else {
                      mSpnState = GetSpnFsmState.READ_SPN_SHORT_CPHS;
                  }
  
                  if (mSpnState == GetSpnFsmState.READ_SPN_SHORT_CPHS) {
                      mFh.loadEFTransparent(
                              EF_SPN_SHORT_CPHS, obtainMessage(EVENT_GET_SPN_DONE));
                      mRecordsToLoad++;
                  }
                  break;
              case READ_SPN_SHORT_CPHS:
                  if (ar != null && ar.exception == null) {
                      data = (byte[]) ar.result;
  
                      setServiceProviderName(IccUtils.adnStringFieldToString(
                                  data, 0, data.length));
                      // for card double-check and brand override
                      // we have to do this:
                      final String spn = getServiceProviderName();
  
                      if (spn == null || spn.length() == 0) {
                          if (DBG) log("No SPN loaded in either CHPS or 3GPP");
                      } else {
                          // Display CPHS Operator Name only when not roaming
                          mCarrierNameDisplayCondition = 0;
  
                          if (DBG) log("Load EF_SPN_SHORT_CPHS: " + spn);
                          mTelephonyManager.setSimOperatorNameForPhone(
                                  mParentApp.getPhoneId(), spn);
                      }
                  } else {
                      setServiceProviderName(null);
                      if (DBG) log("No SPN loaded in either CHPS or 3GPP");
                  }
  
                  mSpnState = GetSpnFsmState.IDLE;
                  break;
              default:
                  mSpnState = GetSpnFsmState.IDLE;
          }
      }
  
      /**
       * Parse TS 51.011 EF[SPDI] record
       * This record contains the list of numeric network IDs that
       * are treated specially when determining SPN display
       */
      private void parseEfSpdi(byte[] data) {
          SimTlv tlv = new SimTlv(data, 0, data.length);
  
          byte[] plmnEntries = null;
  
          for ( ; tlv.isValidObject() ; tlv.nextObject()) {
              // Skip SPDI tag, if existant
              if (tlv.getTag() == TAG_SPDI) {
                tlv = new SimTlv(tlv.getData(), 0, tlv.getData().length);
              }
              // There should only be one TAG_SPDI_PLMN_LIST
              if (tlv.getTag() == TAG_SPDI_PLMN_LIST) {
                  plmnEntries = tlv.getData();
                  break;
              }
          }
  
          if (plmnEntries == null) {
              return;
          }
  
          List<String> tmpSpdi = new ArrayList<>(plmnEntries.length / 3);
          for (int i = 0; i + 2 < plmnEntries.length; i += 3) {
              String plmnCode = IccUtils.bcdPlmnToString(plmnEntries, i);
              if (!TextUtils.isEmpty(plmnCode)) {
                  log("EF_SPDI PLMN: " + plmnCode);
                  tmpSpdi.add(plmnCode);
              }
          }
          mSpdi = (String[]) tmpSpdi.toArray();
      }
  
      /**
       * convert a byte array of packed plmns to an array of strings
       */
      private String[] parseBcdPlmnList(byte[] data, String description) {
          final int packedBcdPlmnLenBytes = 3;
          log("Received " + description + " PLMNs, raw=" + IccUtils.bytesToHexString(data));
          if (data.length == 0 || (data.length % packedBcdPlmnLenBytes) != 0) {
              loge("Received invalid " + description + " PLMN list");
              return null;
          }
          int numPlmns = data.length / packedBcdPlmnLenBytes;
          int numValidPlmns = 0;
          String[] parsed = new String[numPlmns];
          for (int i = 0; i < numPlmns; i++) {
              parsed[numValidPlmns] = IccUtils.bcdPlmnToString(data, i * packedBcdPlmnLenBytes);
              // we count the valid (non empty) records and only increment if valid
              if (!TextUtils.isEmpty(parsed[numValidPlmns])) numValidPlmns++;
          }
          String[] ret = Arrays.copyOf(parsed, numValidPlmns);
          if (VDBG) logv(description + " PLMNs: " + Arrays.toString(ret));
          return ret;
      }
  
      /**
       * check to see if Mailbox Number is allocated and activated in CPHS SST
       */
      @UnsupportedAppUsage
      private boolean isCphsMailboxEnabled() {
          if (mCphsInfo == null)  return false;
          return ((mCphsInfo[1] & CPHS_SST_MBN_MASK) == CPHS_SST_MBN_ENABLED );
      }
  
      @UnsupportedAppUsage
      @Override
      protected void log(String s) {
          Rlog.d(LOG_TAG, "[SIMRecords] " + s);
      }
  
      @UnsupportedAppUsage
      @Override
      protected void loge(String s) {
          Rlog.e(LOG_TAG, "[SIMRecords] " + s);
      }
  
      protected void logw(String s, Throwable tr) {
          Rlog.w(LOG_TAG, "[SIMRecords] " + s, tr);
      }
  
      @UnsupportedAppUsage
      protected void logv(String s) {
          Rlog.v(LOG_TAG, "[SIMRecords] " + s);
      }
  
      /**
       * Return true if "Restriction of menu options for manual PLMN selection"
       * bit is set or EF_CSP data is unavailable, return false otherwise.
       */
      @Override
      public boolean isCspPlmnEnabled() {
          return mCspPlmnEnabled;
      }
  
      /**
       * Parse EF_CSP data and check if
       * "Restriction of menu options for manual PLMN selection" is
       * Enabled/Disabled
       *
       * @param data EF_CSP hex data.
       */
      private void handleEfCspData(byte[] data) {
          // As per spec CPHS4_2.WW6, CPHS B.4.7.1, EF_CSP contains CPHS defined
          // 18 bytes (i.e 9 service groups info) and additional data specific to
          // operator. The valueAddedServicesGroup is not part of standard
          // services. This is operator specific and can be programmed any where.
          // Normally this is programmed as 10th service after the standard
          // services.
          int usedCspGroups = data.length / 2;
          // This is the "Service Group Number" of "Value Added Services Group".
          byte valueAddedServicesGroup = (byte)0xC0;
  
          mCspPlmnEnabled = true;
          for (int i = 0; i < usedCspGroups; i++) {
               if (data[2 * i] == valueAddedServicesGroup) {
                   log("[CSP] found ValueAddedServicesGroup, value " + data[(2 * i) + 1]);
                   if ((data[(2 * i) + 1] & 0x80) == 0x80) {
                       // Bit 8 is for
                       // "Restriction of menu options for manual PLMN selection".
                       // Operator Selection menu should be enabled.
                       mCspPlmnEnabled = true;
                   } else {
                       mCspPlmnEnabled = false;
                       // Operator Selection menu should be disabled.
                       // Operator Selection Mode should be set to Automatic.
                       log("[CSP] Set Automatic Network Selection");
                       mNetworkSelectionModeAutomaticRegistrants.notifyRegistrants();
                   }
                   return;
               }
          }
  
          log("[CSP] Value Added Service Group (0xC0), not found!");
      }
  
      @Override
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          pw.println("SIMRecords: " + this);
          pw.println(" extends:");
          super.dump(fd, pw, args);
          pw.println(" mVmConfig=" + mVmConfig);
          pw.println(" mCallForwardingStatus=" + mCallForwardingStatus);
          pw.println(" mSpnState=" + mSpnState);
          pw.println(" mCphsInfo=" + mCphsInfo);
          pw.println(" mCspPlmnEnabled=" + mCspPlmnEnabled);
          pw.println(" mEfMWIS[]=" + Arrays.toString(mEfMWIS));
          pw.println(" mEfCPHS_MWI[]=" + Arrays.toString(mEfCPHS_MWI));
          pw.println(" mEfCff[]=" + Arrays.toString(mEfCff));
          pw.println(" mEfCfis[]=" + Arrays.toString(mEfCfis));
          pw.println(" mCarrierNameDisplayCondition=" + mCarrierNameDisplayCondition);
          pw.println(" mSpdi[]=" + mSpdi);
          pw.println(" mUsimServiceTable=" + mUsimServiceTable);
          pw.println(" mGid1=" + mGid1);
          if (mCarrierTestOverride.isInTestMode()) {
              pw.println(" mFakeGid1=" + mCarrierTestOverride.getFakeGid1());
          }
          pw.println(" mGid2=" + mGid2);
          if (mCarrierTestOverride.isInTestMode()) {
              pw.println(" mFakeGid2=" + mCarrierTestOverride.getFakeGid2());
          }
          pw.println(" mPnnHomeName=" + mPnnHomeName);
          if (mCarrierTestOverride.isInTestMode()) {
              pw.println(" mFakePnnHomeName=" + mCarrierTestOverride.getFakePnnHomeName());
          }
          pw.println(" mPlmnActRecords[]=" + Arrays.toString(mPlmnActRecords));
          pw.println(" mOplmnActRecords[]=" + Arrays.toString(mOplmnActRecords));
          pw.println(" mHplmnActRecords[]=" + Arrays.toString(mHplmnActRecords));
          pw.println(" mFplmns[]=" + Arrays.toString(mFplmns));
          pw.println(" mEhplmns[]=" + Arrays.toString(mEhplmns));
          pw.flush();
      }
  }
  