content: /*
   * Copyright (C) 2015 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;
  
  import static com.android.internal.telephony.CommandsInterface.CF_ACTION_DISABLE;
  import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE;
  import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ERASURE;
  import static com.android.internal.telephony.CommandsInterface.CF_ACTION_REGISTRATION;
  import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL;
  import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL_CONDITIONAL;
  import static com.android.internal.telephony.CommandsInterface.CF_REASON_BUSY;
  import static com.android.internal.telephony.CommandsInterface.CF_REASON_NOT_REACHABLE;
  import static com.android.internal.telephony.CommandsInterface.CF_REASON_NO_REPLY;
  import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDITIONAL;
  import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.UnsupportedAppUsage;
  import android.app.ActivityManager;
  import android.content.BroadcastReceiver;
  import android.content.ContentValues;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.SharedPreferences;
  import android.database.SQLException;
  import android.net.Uri;
  import android.os.AsyncResult;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.Message;
  import android.os.PersistableBundle;
  import android.os.PowerManager;
  import android.os.Registrant;
  import android.os.RegistrantList;
  import android.os.ResultReceiver;
  import android.os.SystemProperties;
  import android.os.UserHandle;
  import android.os.WorkSource;
  import android.preference.PreferenceManager;
  import android.provider.Settings;
  import android.provider.Telephony;
  import android.telecom.TelecomManager;
  import android.telecom.VideoProfile;
  import android.telephony.AccessNetworkConstants;
  import android.telephony.CarrierConfigManager;
  import android.telephony.CellLocation;
  import android.telephony.ImsiEncryptionInfo;
  import android.telephony.NetworkScanRequest;
  import android.telephony.PhoneNumberUtils;
  import android.telephony.Rlog;
  import android.telephony.ServiceState;
  import android.telephony.SubscriptionInfo;
  import android.telephony.SubscriptionManager;
  import android.telephony.TelephonyManager;
  import android.telephony.UssdResponse;
  import android.telephony.data.ApnSetting;
  import android.text.TextUtils;
  import android.util.Log;
  import android.util.Pair;
  
  import com.android.ims.ImsManager;
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.telephony.cdma.CdmaMmiCode;
  import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
  import com.android.internal.telephony.dataconnection.DataEnabledSettings;
  import com.android.internal.telephony.dataconnection.DcTracker;
  import com.android.internal.telephony.dataconnection.TransportManager;
  import com.android.internal.telephony.emergency.EmergencyNumberTracker;
  import com.android.internal.telephony.gsm.GsmMmiCode;
  import com.android.internal.telephony.gsm.SuppServiceNotification;
  import com.android.internal.telephony.test.SimulatedRadioControl;
  import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
  import com.android.internal.telephony.uicc.IccException;
  import com.android.internal.telephony.uicc.IccRecords;
  import com.android.internal.telephony.uicc.IccVmNotSupportedException;
  import com.android.internal.telephony.uicc.IsimRecords;
  import com.android.internal.telephony.uicc.IsimUiccRecords;
  import com.android.internal.telephony.uicc.RuimRecords;
  import com.android.internal.telephony.uicc.SIMRecords;
  import com.android.internal.telephony.uicc.UiccCard;
  import com.android.internal.telephony.uicc.UiccCardApplication;
  import com.android.internal.telephony.uicc.UiccController;
  import com.android.internal.telephony.uicc.UiccProfile;
  import com.android.internal.telephony.uicc.UiccSlot;
  import com.android.internal.util.ArrayUtils;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  /**
   * {@hide}
   */
  public class GsmCdmaPhone extends Phone {
      // NOTE that LOG_TAG here is "GsmCdma", which means that log messages
      // from this file will go into the radio log rather than the main
      // log.  (Use "adb logcat -b radio" to see them.)
      public static final String LOG_TAG = "GsmCdmaPhone";
      private static final boolean DBG = true;
      private static final boolean VDBG = false; /* STOPSHIP if true */
  
      /** Required magnitude change between unsolicited SignalStrength reports. */
      private static final int REPORTING_HYSTERESIS_DB = 2;
      /** Required throughput change between unsolicited LinkCapacityEstimate reports. */
      private static final int REPORTING_HYSTERESIS_KBPS = 50;
      /** Minimum time between unsolicited SignalStrength and LinkCapacityEstimate reports. */
      private static final int REPORTING_HYSTERESIS_MILLIS = 3000;
  
      //GSM
      // Key used to read/write voice mail number
      private static final String VM_NUMBER = "vm_number_key";
      // Key used to read/write the SIM IMSI used for storing the voice mail
      private static final String VM_SIM_IMSI = "vm_sim_imsi_key";
      /** List of Registrants to receive Supplementary Service Notifications. */
      private RegistrantList mSsnRegistrants = new RegistrantList();
  
      //CDMA
      // Default Emergency Callback Mode exit timer
      private static final int DEFAULT_ECM_EXIT_TIMER_VALUE = 300000;
      private static final String VM_NUMBER_CDMA = "vm_number_key_cdma";
      public static final int RESTART_ECM_TIMER = 0; // restart Ecm timer
      public static final int CANCEL_ECM_TIMER = 1; // cancel Ecm timer
      private static final String PREFIX_WPS = "*272";
      private CdmaSubscriptionSourceManager mCdmaSSM;
      public int mCdmaSubscriptionSource = CdmaSubscriptionSourceManager.SUBSCRIPTION_SOURCE_UNKNOWN;
      private PowerManager.WakeLock mWakeLock;
      // mEcmExitRespRegistrant is informed after the phone has been exited
      @UnsupportedAppUsage
      private Registrant mEcmExitRespRegistrant;
      private String mEsn;
      private String mMeid;
      // string to define how the carrier specifies its own ota sp number
      private String mCarrierOtaSpNumSchema;
  
      // A runnable which is used to automatically exit from Ecm after a period of time.
      private Runnable mExitEcmRunnable = new Runnable() {
          @Override
          public void run() {
              exitEmergencyCallbackMode();
          }
      };
      public static final String PROPERTY_CDMA_HOME_OPERATOR_NUMERIC =
              "ro.cdma.home.operator.numeric";
  
      //CDMALTE
      /** PHONE_TYPE_CDMA_LTE in addition to RuimRecords needs access to SIMRecords and
       * IsimUiccRecords
       */
      private SIMRecords mSimRecords;
  
      //Common
      // Instance Variables
      @UnsupportedAppUsage
      private IsimUiccRecords mIsimUiccRecords;
      @UnsupportedAppUsage
      public GsmCdmaCallTracker mCT;
      @UnsupportedAppUsage
      public ServiceStateTracker mSST;
      public EmergencyNumberTracker mEmergencyNumberTracker;
      @UnsupportedAppUsage
      private ArrayList <MmiCode> mPendingMMIs = new ArrayList<MmiCode>();
      private IccPhoneBookInterfaceManager mIccPhoneBookIntManager;
  
      private int mPrecisePhoneType;
  
      // mEcmTimerResetRegistrants are informed after Ecm timer is canceled or re-started
      private final RegistrantList mEcmTimerResetRegistrants = new RegistrantList();
  
      private String mImei;
      private String mImeiSv;
      private String mVmNumber;
  
      // Create Cfu (Call forward unconditional) so that dialing number &
      // mOnComplete (Message object passed by client) can be packed &
      // given as a single Cfu object as user data to RIL.
      private static class Cfu {
          final String mSetCfNumber;
          final Message mOnComplete;
  
          @UnsupportedAppUsage
          Cfu(String cfNumber, Message onComplete) {
              mSetCfNumber = cfNumber;
              mOnComplete = onComplete;
          }
      }
  
      @UnsupportedAppUsage
      private IccSmsInterfaceManager mIccSmsInterfaceManager;
  
      private boolean mResetModemOnRadioTechnologyChange = false;
  
      private int mRilVersion;
      private boolean mBroadcastEmergencyCallStateChanges = false;
      private CarrierKeyDownloadManager mCDM;
      private CarrierInfoManager mCIM;
  
      private final SettingsObserver mSettingsObserver;
  
      // Constructors
  
      public GsmCdmaPhone(Context context, CommandsInterface ci, PhoneNotifier notifier, int phoneId,
                          int precisePhoneType, TelephonyComponentFactory telephonyComponentFactory) {
          this(context, ci, notifier, false, phoneId, precisePhoneType, telephonyComponentFactory);
      }
  
      public GsmCdmaPhone(Context context, CommandsInterface ci, PhoneNotifier notifier,
                          boolean unitTestMode, int phoneId, int precisePhoneType,
                          TelephonyComponentFactory telephonyComponentFactory) {
          super(precisePhoneType == PhoneConstants.PHONE_TYPE_GSM ? "GSM" : "CDMA",
                  notifier, context, ci, unitTestMode, phoneId, telephonyComponentFactory);
  
          // phone type needs to be set before other initialization as other objects rely on it
          mPrecisePhoneType = precisePhoneType;
          initOnce(ci);
          initRatSpecific(precisePhoneType);
          // CarrierSignalAgent uses CarrierActionAgent in construction so it needs to be created
          // after CarrierActionAgent.
          mCarrierActionAgent = mTelephonyComponentFactory.inject(CarrierActionAgent.class.getName())
                  .makeCarrierActionAgent(this);
          mCarrierSignalAgent = mTelephonyComponentFactory.inject(CarrierSignalAgent.class.getName())
                  .makeCarrierSignalAgent(this);
          mTransportManager = mTelephonyComponentFactory.inject(TransportManager.class.getName())
                  .makeTransportManager(this);
          mSST = mTelephonyComponentFactory.inject(ServiceStateTracker.class.getName())
                  .makeServiceStateTracker(this, this.mCi);
          mEmergencyNumberTracker = mTelephonyComponentFactory
                  .inject(EmergencyNumberTracker.class.getName()).makeEmergencyNumberTracker(
                  this, this.mCi);
          mDataEnabledSettings = mTelephonyComponentFactory
                  .inject(DataEnabledSettings.class.getName()).makeDataEnabledSettings(this);
  
          // DcTracker uses SST so needs to be created after it is instantiated
          for (int transport : mTransportManager.getAvailableTransports()) {
              mDcTrackers.put(transport, mTelephonyComponentFactory.inject(DcTracker.class.getName())
                      .makeDcTracker(this, transport));
          }
  
          mCarrierResolver = mTelephonyComponentFactory.inject(CarrierResolver.class.getName())
                  .makeCarrierResolver(this);
  
          getCarrierActionAgent().registerForCarrierAction(
                  CarrierActionAgent.CARRIER_ACTION_SET_METERED_APNS_ENABLED, this,
                  EVENT_SET_CARRIER_DATA_ENABLED, null, false);
  
          mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
          mDeviceStateMonitor = mTelephonyComponentFactory.inject(DeviceStateMonitor.class.getName())
                  .makeDeviceStateMonitor(this);
  
          mSST.registerForVoiceRegStateOrRatChanged(this, EVENT_VRS_OR_RAT_CHANGED, null);
  
          mSettingsObserver = new SettingsObserver(context, this);
          mSettingsObserver.observe(
                  Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
                  EVENT_DEVICE_PROVISIONED_CHANGE);
          mSettingsObserver.observe(
                  Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED),
                  EVENT_DEVICE_PROVISIONING_DATA_SETTING_CHANGE);
  
          loadTtyMode();
          logd("GsmCdmaPhone: constructor: sub = " + mPhoneId);
      }
  
      private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              Rlog.d(LOG_TAG, "mBroadcastReceiver: action " + intent.getAction());
              String action = intent.getAction();
              if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)) {
                  // Only handle carrier config changes for this phone id.
                  if (mPhoneId == intent.getIntExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, -1)) {
                      sendMessage(obtainMessage(EVENT_CARRIER_CONFIG_CHANGED));
                  }
              } else if (TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED.equals(action)) {
                  int ttyMode = intent.getIntExtra(
                          TelecomManager.EXTRA_CURRENT_TTY_MODE, TelecomManager.TTY_MODE_OFF);
                  updateTtyMode(ttyMode);
              } else if (TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED.equals(action)) {
                  int newPreferredTtyMode = intent.getIntExtra(
                          TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF);
                  updateUiTtyMode(newPreferredTtyMode);
              }
          }
      };
  
      private void initOnce(CommandsInterface ci) {
          if (ci instanceof SimulatedRadioControl) {
              mSimulatedRadioControl = (SimulatedRadioControl) ci;
          }
  
          mCT = mTelephonyComponentFactory.inject(GsmCdmaCallTracker.class.getName())
                  .makeGsmCdmaCallTracker(this);
          mIccPhoneBookIntManager = mTelephonyComponentFactory
                  .inject(IccPhoneBookInterfaceManager.class.getName())
                  .makeIccPhoneBookInterfaceManager(this);
          PowerManager pm
                  = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
          mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
          mIccSmsInterfaceManager = mTelephonyComponentFactory
                  .inject(IccSmsInterfaceManager.class.getName())
                  .makeIccSmsInterfaceManager(this);
  
          mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
          mCi.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
          mCi.registerForOn(this, EVENT_RADIO_ON, null);
          mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
          mCi.setOnSuppServiceNotification(this, EVENT_SSN, null);
  
          //GSM
          mCi.setOnUSSD(this, EVENT_USSD, null);
          mCi.setOnSs(this, EVENT_SS, null);
  
          //CDMA
          mCdmaSSM = mTelephonyComponentFactory.inject(CdmaSubscriptionSourceManager.class.getName())
                  .getCdmaSubscriptionSourceManagerInstance(mContext,
                  mCi, this, EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null);
          mCi.setEmergencyCallbackMode(this, EVENT_EMERGENCY_CALLBACK_MODE_ENTER, null);
          mCi.registerForExitEmergencyCallbackMode(this, EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE,
                  null);
          mCi.registerForModemReset(this, EVENT_MODEM_RESET, null);
          // get the string that specifies the carrier OTA Sp number
          mCarrierOtaSpNumSchema = TelephonyManager.from(mContext).getOtaSpNumberSchemaForPhone(
                  getPhoneId(), "");
  
          mResetModemOnRadioTechnologyChange = SystemProperties.getBoolean(
                  TelephonyProperties.PROPERTY_RESET_ON_RADIO_TECH_CHANGE, false);
  
          mCi.registerForRilConnected(this, EVENT_RIL_CONNECTED, null);
          mCi.registerForVoiceRadioTechChanged(this, EVENT_VOICE_RADIO_TECH_CHANGED, null);
          IntentFilter filter = new IntentFilter(
                  CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
          filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
          filter.addAction(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
          mContext.registerReceiver(mBroadcastReceiver, filter);
  
          mCDM = new CarrierKeyDownloadManager(this);
          mCIM = new CarrierInfoManager();
      }
  
      private void initRatSpecific(int precisePhoneType) {
          mPendingMMIs.clear();
          mIccPhoneBookIntManager.updateIccRecords(null);
          mEsn = null;
          mMeid = null;
  
          mPrecisePhoneType = precisePhoneType;
          logd("Precise phone type " + mPrecisePhoneType);
  
          TelephonyManager tm = TelephonyManager.from(mContext);
          UiccProfile uiccProfile = getUiccProfile();
          if (isPhoneTypeGsm()) {
              mCi.setPhoneType(PhoneConstants.PHONE_TYPE_GSM);
              tm.setPhoneType(getPhoneId(), PhoneConstants.PHONE_TYPE_GSM);
              if (uiccProfile != null) {
                  uiccProfile.setVoiceRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS);
              }
          } else {
              mCdmaSubscriptionSource = mCdmaSSM.getCdmaSubscriptionSource();
              // This is needed to handle phone process crashes
              mIsPhoneInEcmState = getInEcmMode();
              if (mIsPhoneInEcmState) {
                  // Send a message which will invoke handleExitEmergencyCallbackMode
                  mCi.exitEmergencyCallbackMode(
                          obtainMessage(EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE));
              }
  
              mCi.setPhoneType(PhoneConstants.PHONE_TYPE_CDMA);
              tm.setPhoneType(getPhoneId(), PhoneConstants.PHONE_TYPE_CDMA);
              if (uiccProfile != null) {
                  uiccProfile.setVoiceRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT);
              }
              // Sets operator properties by retrieving from build-time system property
              String operatorAlpha = SystemProperties.get("ro.cdma.home.operator.alpha");
              String operatorNumeric = SystemProperties.get(PROPERTY_CDMA_HOME_OPERATOR_NUMERIC);
              logd("init: operatorAlpha='" + operatorAlpha
                      + "' operatorNumeric='" + operatorNumeric + "'");
              if (!TextUtils.isEmpty(operatorAlpha)) {
                  logd("init: set 'gsm.sim.operator.alpha' to operator='" + operatorAlpha + "'");
                  tm.setSimOperatorNameForPhone(mPhoneId, operatorAlpha);
              }
              if (!TextUtils.isEmpty(operatorNumeric)) {
                  logd("init: set 'gsm.sim.operator.numeric' to operator='" + operatorNumeric +
                          "'");
                  logd("update icc_operator_numeric=" + operatorNumeric);
                  tm.setSimOperatorNumericForPhone(mPhoneId, operatorNumeric);
  
                  SubscriptionController.getInstance().setMccMnc(operatorNumeric, getSubId());
                  // Sets iso country property by retrieving from build-time system property
                  setIsoCountryProperty(operatorNumeric);
                  // Updates MCC MNC device configuration information
                  logd("update mccmnc=" + operatorNumeric);
                  MccTable.updateMccMncConfiguration(mContext, operatorNumeric);
              }
  
              // Sets current entry in the telephony carrier table
              updateCurrentCarrierInProvider(operatorNumeric);
          }
      }
  
      //CDMA
      /**
       * Sets PROPERTY_ICC_OPERATOR_ISO_COUNTRY property
       *
       */
      private void setIsoCountryProperty(String operatorNumeric) {
          TelephonyManager tm = TelephonyManager.from(mContext);
          if (TextUtils.isEmpty(operatorNumeric)) {
              logd("setIsoCountryProperty: clear 'gsm.sim.operator.iso-country'");
              tm.setSimCountryIsoForPhone(mPhoneId, "");
              SubscriptionController.getInstance().setCountryIso("", getSubId());
          } else {
              String iso = "";
              try {
                  iso = MccTable.countryCodeForMcc(operatorNumeric.substring(0, 3));
              } catch (StringIndexOutOfBoundsException ex) {
                  Rlog.e(LOG_TAG, "setIsoCountryProperty: countryCodeForMcc error", ex);
              }
  
              logd("setIsoCountryProperty: set 'gsm.sim.operator.iso-country' to iso=" + iso);
              tm.setSimCountryIsoForPhone(mPhoneId, iso);
              SubscriptionController.getInstance().setCountryIso(iso, getSubId());
          }
      }
  
      @UnsupportedAppUsage
      public boolean isPhoneTypeGsm() {
          return mPrecisePhoneType == PhoneConstants.PHONE_TYPE_GSM;
      }
  
      public boolean isPhoneTypeCdma() {
          return mPrecisePhoneType == PhoneConstants.PHONE_TYPE_CDMA;
      }
  
      public boolean isPhoneTypeCdmaLte() {
          return mPrecisePhoneType == PhoneConstants.PHONE_TYPE_CDMA_LTE;
      }
  
      private void switchPhoneType(int precisePhoneType) {
          removeCallbacks(mExitEcmRunnable);
  
          initRatSpecific(precisePhoneType);
  
          mSST.updatePhoneType();
          setPhoneName(precisePhoneType == PhoneConstants.PHONE_TYPE_GSM ? "GSM" : "CDMA");
          onUpdateIccAvailability();
          // if is possible that onUpdateIccAvailability() does not unregister and re-register for
          // ICC events, for example if mUiccApplication does not change which can happen if phone
          // type is transitioning from CDMA to GSM but 3gpp2 application was not available.
          // To handle such cases, unregister and re-register here. They still need to be called in
          // onUpdateIccAvailability(), since in normal cases register/unregister calls can be on
          // different IccRecords objects. Here they are on the same IccRecords object.
          unregisterForIccRecordEvents();
          registerForIccRecordEvents();
  
          mCT.updatePhoneType();
  
          int radioState = mCi.getRadioState();
          if (radioState != TelephonyManager.RADIO_POWER_UNAVAILABLE) {
              handleRadioAvailable();
              if (radioState == TelephonyManager.RADIO_POWER_ON) {
                  handleRadioOn();
              }
          }
          if (radioState != TelephonyManager.RADIO_POWER_ON) {
              handleRadioOffOrNotAvailable();
          }
      }
  
      @Override
      protected void finalize() {
          if(DBG) logd("GsmCdmaPhone finalized");
          if (mWakeLock != null && mWakeLock.isHeld()) {
              Rlog.e(LOG_TAG, "UNEXPECTED; mWakeLock is held when finalizing.");
              mWakeLock.release();
          }
      }
  
      @UnsupportedAppUsage
      @Override
      public ServiceState getServiceState() {
          if (mSST == null || mSST.mSS.getState() != ServiceState.STATE_IN_SERVICE) {
              if (mImsPhone != null) {
                  return mergeServiceStates((mSST == null) ? new ServiceState() : mSST.mSS,
                          mImsPhone.getServiceState());
              }
          }
  
          if (mSST != null) {
              return mSST.mSS;
          } else {
              // avoid potential NPE in EmergencyCallHelper during Phone switch
              return new ServiceState();
          }
      }
  
      @Override
      public void getCellLocation(WorkSource workSource, Message rspMsg) {
          mSST.requestCellLocation(workSource, rspMsg);
      }
  
      @UnsupportedAppUsage
      @Override
      public PhoneConstants.State getState() {
          if (mImsPhone != null) {
              PhoneConstants.State imsState = mImsPhone.getState();
              if (imsState != PhoneConstants.State.IDLE) {
                  return imsState;
              }
          }
  
          return mCT.mState;
      }
  
      @UnsupportedAppUsage
      @Override
      public int getPhoneType() {
          if (mPrecisePhoneType == PhoneConstants.PHONE_TYPE_GSM) {
              return PhoneConstants.PHONE_TYPE_GSM;
          } else {
              return PhoneConstants.PHONE_TYPE_CDMA;
          }
      }
  
      @Override
      public ServiceStateTracker getServiceStateTracker() {
          return mSST;
      }
  
      @Override
      public EmergencyNumberTracker getEmergencyNumberTracker() {
          return mEmergencyNumberTracker;
      }
  
      @UnsupportedAppUsage
      @Override
      public CallTracker getCallTracker() {
          return mCT;
      }
  
      @Override
      public TransportManager getTransportManager() {
          return mTransportManager;
      }
  
      @Override
      public void updateVoiceMail() {
          if (isPhoneTypeGsm()) {
              int countVoiceMessages = 0;
              IccRecords r = mIccRecords.get();
              if (r != null) {
                  // get voice mail count from SIM
                  countVoiceMessages = r.getVoiceMessageCount();
              }
              if (countVoiceMessages == IccRecords.DEFAULT_VOICE_MESSAGE_COUNT) {
                  countVoiceMessages = getStoredVoiceMessageCount();
              }
              logd("updateVoiceMail countVoiceMessages = " + countVoiceMessages
                      + " subId " + getSubId());
              setVoiceMessageCount(countVoiceMessages);
          } else {
              setVoiceMessageCount(getStoredVoiceMessageCount());
          }
      }
  
      @Override
      public List<? extends MmiCode>
      getPendingMmiCodes() {
          return mPendingMMIs;
      }
  
      @Override
      public PhoneConstants.DataState getDataConnectionState(String apnType) {
          PhoneConstants.DataState ret = PhoneConstants.DataState.DISCONNECTED;
  
          if (mSST == null) {
              // Radio Technology Change is ongoning, dispose() and removeReferences() have
              // already been called
  
              ret = PhoneConstants.DataState.DISCONNECTED;
          } else if (mSST.getCurrentDataConnectionState() != ServiceState.STATE_IN_SERVICE
                  && (isPhoneTypeCdma() || isPhoneTypeCdmaLte() ||
                  (isPhoneTypeGsm() && !apnType.equals(PhoneConstants.APN_TYPE_EMERGENCY)))) {
              // If we're out of service, open TCP sockets may still work
              // but no data will flow
  
              // Emergency APN is available even in Out Of Service
              // Pass the actual State of EPDN
  
              ret = PhoneConstants.DataState.DISCONNECTED;
          } else { /* mSST.gprsState == ServiceState.STATE_IN_SERVICE */
              int currentTransport = mTransportManager.getCurrentTransport(
                      ApnSetting.getApnTypesBitmaskFromString(apnType));
              if (getDcTracker(currentTransport) != null) {
                  switch (getDcTracker(currentTransport).getState(apnType)) {
                      case CONNECTED:
                      case DISCONNECTING:
                          if (mCT.mState != PhoneConstants.State.IDLE
                                  && !mSST.isConcurrentVoiceAndDataAllowed()) {
                              ret = PhoneConstants.DataState.SUSPENDED;
                          } else {
                              ret = PhoneConstants.DataState.CONNECTED;
                          }
                          break;
                      case CONNECTING:
                          ret = PhoneConstants.DataState.CONNECTING;
                          break;
                      default:
                          ret = PhoneConstants.DataState.DISCONNECTED;
                  }
              }
          }
  
          logd("getDataConnectionState apnType=" + apnType + " ret=" + ret);
          return ret;
      }
  
      @Override
      public DataActivityState getDataActivityState() {
          DataActivityState ret = DataActivityState.NONE;
  
          if (mSST.getCurrentDataConnectionState() == ServiceState.STATE_IN_SERVICE
                  && getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) != null) {
              switch (getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).getActivity()) {
                  case DATAIN:
                      ret = DataActivityState.DATAIN;
                  break;
  
                  case DATAOUT:
                      ret = DataActivityState.DATAOUT;
                  break;
  
                  case DATAINANDOUT:
                      ret = DataActivityState.DATAINANDOUT;
                  break;
  
                  case DORMANT:
                      ret = DataActivityState.DORMANT;
                  break;
  
                  default:
                      ret = DataActivityState.NONE;
                  break;
              }
          }
  
          return ret;
      }
  
      /**
       * Notify any interested party of a Phone state change
       * {@link com.android.internal.telephony.PhoneConstants.State}
       */
      public void notifyPhoneStateChanged() {
          mNotifier.notifyPhoneState(this);
      }
  
      /**
       * Notify registrants of a change in the call state. This notifies changes in
       * {@link com.android.internal.telephony.Call.State}. Use this when changes
       * in the precise call state are needed, else use notifyPhoneStateChanged.
       */
      @UnsupportedAppUsage
      public void notifyPreciseCallStateChanged() {
          /* we'd love it if this was package-scoped*/
          super.notifyPreciseCallStateChangedP();
      }
  
      public void notifyNewRingingConnection(Connection c) {
          super.notifyNewRingingConnectionP(c);
      }
  
      public void notifyDisconnect(Connection cn) {
          mDisconnectRegistrants.notifyResult(cn);
  
          mNotifier.notifyDisconnectCause(this, cn.getDisconnectCause(),
                  cn.getPreciseDisconnectCause());
      }
  
      public void notifyUnknownConnection(Connection cn) {
          super.notifyUnknownConnectionP(cn);
      }
  
      @Override
      public boolean isInEmergencyCall() {
          if (isPhoneTypeGsm()) {
              return false;
          } else {
              return mCT.isInEmergencyCall();
          }
      }
  
      @Override
      protected void setIsInEmergencyCall() {
          if (!isPhoneTypeGsm()) {
              mCT.setIsInEmergencyCall();
          }
      }
  
      @Override
      public boolean isInEmergencySmsMode() {
          return super.isInEmergencySmsMode()
                  || (mImsPhone != null && mImsPhone.isInEmergencySmsMode());
      }
  
      //CDMA
      private void sendEmergencyCallbackModeChange(){
          //Send an Intent
          Intent intent = new Intent(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
          intent.putExtra(PhoneConstants.PHONE_IN_ECM_STATE, isInEcm());
          SubscriptionManager.putPhoneIdAndSubIdExtra(intent, getPhoneId());
          ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
          logi("sendEmergencyCallbackModeChange");
      }
  
      @Override
      public void sendEmergencyCallStateChange(boolean callActive) {
          if (!isPhoneTypeCdma()) {
              // It possible that this method got called from ImsPhoneCallTracker#
              logi("sendEmergencyCallbackModeChange - skip for non-cdma");
              return;
          }
          if (mBroadcastEmergencyCallStateChanges) {
              Intent intent = new Intent(TelephonyIntents.ACTION_EMERGENCY_CALL_STATE_CHANGED);
              intent.putExtra(PhoneConstants.PHONE_IN_EMERGENCY_CALL, callActive);
              SubscriptionManager.putPhoneIdAndSubIdExtra(intent, getPhoneId());
              ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
              if (DBG) Rlog.d(LOG_TAG, "sendEmergencyCallStateChange: callActive " + callActive);
          }
      }
  
      @Override
      public void setBroadcastEmergencyCallStateChanges(boolean broadcast) {
          mBroadcastEmergencyCallStateChanges = broadcast;
      }
  
      public void notifySuppServiceFailed(SuppService code) {
          mSuppServiceFailedRegistrants.notifyResult(code);
      }
  
      @UnsupportedAppUsage
      public void notifyServiceStateChanged(ServiceState ss) {
          super.notifyServiceStateChangedP(ss);
      }
  
      /**
       * Notify that the CellLocation has changed.
       *
       * @param cl the new CellLocation
       */
      public void notifyLocationChanged(CellLocation cl) {
          mNotifier.notifyCellLocation(this, cl);
      }
  
      @Override
      public void notifyCallForwardingIndicator() {
          mNotifier.notifyCallForwardingChanged(this);
      }
  
      @Override
      public void registerForSuppServiceNotification(
              Handler h, int what, Object obj) {
          mSsnRegistrants.addUnique(h, what, obj);
          if (mSsnRegistrants.size() == 1) mCi.setSuppServiceNotifications(true, null);
      }
  
      @Override
      public void unregisterForSuppServiceNotification(Handler h) {
          mSsnRegistrants.remove(h);
          if (mSsnRegistrants.size() == 0) mCi.setSuppServiceNotifications(false, null);
      }
  
      @Override
      public void registerForSimRecordsLoaded(Handler h, int what, Object obj) {
          mSimRecordsLoadedRegistrants.addUnique(h, what, obj);
      }
  
      @Override
      public void unregisterForSimRecordsLoaded(Handler h) {
          mSimRecordsLoadedRegistrants.remove(h);
      }
  
      @Override
      public void acceptCall(int videoState) throws CallStateException {
          Phone imsPhone = mImsPhone;
          if ( imsPhone != null && imsPhone.getRingingCall().isRinging() ) {
              imsPhone.acceptCall(videoState);
          } else {
              mCT.acceptCall();
          }
      }
  
      @Override
      public void rejectCall() throws CallStateException {
          mCT.rejectCall();
      }
  
      @Override
      public void switchHoldingAndActive() throws CallStateException {
          mCT.switchWaitingOrHoldingAndActive();
      }
  
      @Override
      public String getIccSerialNumber() {
          IccRecords r = mIccRecords.get();
          if (!isPhoneTypeGsm() && r == null) {
              // to get ICCID form SIMRecords because it is on MF.
              r = mUiccController.getIccRecords(mPhoneId, UiccController.APP_FAM_3GPP);
          }
          return (r != null) ? r.getIccId() : null;
      }
  
      @Override
      public String getFullIccSerialNumber() {
          IccRecords r = mIccRecords.get();
          if (!isPhoneTypeGsm() && r == null) {
              // to get ICCID form SIMRecords because it is on MF.
              r = mUiccController.getIccRecords(mPhoneId, UiccController.APP_FAM_3GPP);
          }
          return (r != null) ? r.getFullIccId() : null;
      }
  
      @Override
      public boolean canConference() {
          if (mImsPhone != null && mImsPhone.canConference()) {
              return true;
          }
          if (isPhoneTypeGsm()) {
              return mCT.canConference();
          } else {
              loge("canConference: not possible in CDMA");
              return false;
          }
      }
  
      @Override
      public void conference() {
          if (mImsPhone != null && mImsPhone.canConference()) {
              logd("conference() - delegated to IMS phone");
              try {
                  mImsPhone.conference();
              } catch (CallStateException e) {
                  loge(e.toString());
              }
              return;
          }
          if (isPhoneTypeGsm()) {
              mCT.conference();
          } else {
              // three way calls in CDMA will be handled by feature codes
              loge("conference: not possible in CDMA");
          }
      }
  
      @Override
      public void enableEnhancedVoicePrivacy(boolean enable, Message onComplete) {
          if (isPhoneTypeGsm()) {
              loge("enableEnhancedVoicePrivacy: not expected on GSM");
          } else {
              mCi.setPreferredVoicePrivacy(enable, onComplete);
          }
      }
  
      @Override
      public void getEnhancedVoicePrivacy(Message onComplete) {
          if (isPhoneTypeGsm()) {
              loge("getEnhancedVoicePrivacy: not expected on GSM");
          } else {
              mCi.getPreferredVoicePrivacy(onComplete);
          }
      }
  
      @Override
      public void clearDisconnected() {
          mCT.clearDisconnected();
      }
  
      @Override
      public boolean canTransfer() {
          if (isPhoneTypeGsm()) {
              return mCT.canTransfer();
          } else {
              loge("canTransfer: not possible in CDMA");
              return false;
          }
      }
  
      @Override
      public void explicitCallTransfer() {
          if (isPhoneTypeGsm()) {
              mCT.explicitCallTransfer();
          } else {
              loge("explicitCallTransfer: not possible in CDMA");
          }
      }
  
      @Override
      public GsmCdmaCall getForegroundCall() {
          return mCT.mForegroundCall;
      }
  
      @Override
      public GsmCdmaCall getBackgroundCall() {
          return mCT.mBackgroundCall;
      }
  
      @Override
      public Call getRingingCall() {
          Phone imsPhone = mImsPhone;
          // It returns the ringing call of ImsPhone if the ringing call of GSMPhone isn't ringing.
          // In CallManager.registerPhone(), it always registers ringing call of ImsPhone, because
          // the ringing call of GSMPhone isn't ringing. Consequently, it can't answer GSM call
          // successfully by invoking TelephonyManager.answerRingingCall() since the implementation
          // in PhoneInterfaceManager.answerRingingCallInternal() could not get the correct ringing
          // call from CallManager. So we check the ringing call state of imsPhone first as
          // accpetCall() does.
          if ( imsPhone != null && imsPhone.getRingingCall().isRinging()) {
              return imsPhone.getRingingCall();
          }
          return mCT.mRingingCall;
      }
  
      /**
       * ImsService reports "IN_SERVICE" for its voice registration state even if the device
       * has lost the physical link to the tower. This helper method merges the IMS and modem
       * ServiceState, only overriding the voice registration state when we are registered to IMS over
       * IWLAN. In this case the voice registration state will always be "OUT_OF_SERVICE", so override
       * the voice registration state with the data registration state.
       */
      private ServiceState mergeServiceStates(ServiceState baseSs, ServiceState imsSs) {
          // "IN_SERVICE" in this case means IMS is registered.
          if (imsSs.getVoiceRegState() != ServiceState.STATE_IN_SERVICE) {
              return baseSs;
          }
  
          if (imsSs.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
              ServiceState newSs = new ServiceState(baseSs);
              // Voice override for IWLAN. In this case, voice registration is OUT_OF_SERVICE, but
              // the data RAT is IWLAN, so use that as a basis for determining whether or not the
              // physical link is available.
              newSs.setVoiceRegState(baseSs.getDataRegState());
              newSs.setEmergencyOnly(false); // only get here if voice is IN_SERVICE
              return newSs;
          }
  
          return baseSs;
      }
  
      private boolean handleCallDeflectionIncallSupplementaryService(
              String dialString) {
          if (dialString.length() > 1) {
              return false;
          }
  
          if (getRingingCall().getState() != GsmCdmaCall.State.IDLE) {
              if (DBG) logd("MmiCode 0: rejectCall");
              try {
                  mCT.rejectCall();
              } catch (CallStateException e) {
                  if (DBG) Rlog.d(LOG_TAG,
                          "reject failed", e);
                  notifySuppServiceFailed(Phone.SuppService.REJECT);
              }
          } else if (getBackgroundCall().getState() != GsmCdmaCall.State.IDLE) {
              if (DBG) logd("MmiCode 0: hangupWaitingOrBackground");
              mCT.hangupWaitingOrBackground();
          }
  
          return true;
      }
  
      //GSM
      private boolean handleCallWaitingIncallSupplementaryService(String dialString) {
          int len = dialString.length();
  
          if (len > 2) {
              return false;
          }
  
          GsmCdmaCall call = getForegroundCall();
  
          try {
              if (len > 1) {
                  char ch = dialString.charAt(1);
                  int callIndex = ch - '0';
  
                  if (callIndex >= 1 && callIndex <= GsmCdmaCallTracker.MAX_CONNECTIONS_GSM) {
                      if (DBG) logd("MmiCode 1: hangupConnectionByIndex " + callIndex);
                      mCT.hangupConnectionByIndex(call, callIndex);
                  }
              } else {
                  if (call.getState() != GsmCdmaCall.State.IDLE) {
                      if (DBG) logd("MmiCode 1: hangup foreground");
                      //mCT.hangupForegroundResumeBackground();
                      mCT.hangup(call);
                  } else {
                      if (DBG) logd("MmiCode 1: switchWaitingOrHoldingAndActive");
                      mCT.switchWaitingOrHoldingAndActive();
                  }
              }
          } catch (CallStateException e) {
              if (DBG) Rlog.d(LOG_TAG,
                      "hangup failed", e);
              notifySuppServiceFailed(Phone.SuppService.HANGUP);
          }
  
          return true;
      }
  
      private boolean handleCallHoldIncallSupplementaryService(String dialString) {
          int len = dialString.length();
  
          if (len > 2) {
              return false;
          }
  
          GsmCdmaCall call = getForegroundCall();
  
          if (len > 1) {
              try {
                  char ch = dialString.charAt(1);
                  int callIndex = ch - '0';
                  GsmCdmaConnection conn = mCT.getConnectionByIndex(call, callIndex);
  
                  // GsmCdma index starts at 1, up to 5 connections in a call,
                  if (conn != null && callIndex >= 1 && callIndex <= GsmCdmaCallTracker.MAX_CONNECTIONS_GSM) {
                      if (DBG) logd("MmiCode 2: separate call " + callIndex);
                      mCT.separate(conn);
                  } else {
                      if (DBG) logd("separate: invalid call index " + callIndex);
                      notifySuppServiceFailed(Phone.SuppService.SEPARATE);
                  }
              } catch (CallStateException e) {
                  if (DBG) Rlog.d(LOG_TAG, "separate failed", e);
                  notifySuppServiceFailed(Phone.SuppService.SEPARATE);
              }
          } else {
              try {
                  if (getRingingCall().getState() != GsmCdmaCall.State.IDLE) {
                      if (DBG) logd("MmiCode 2: accept ringing call");
                      mCT.acceptCall();
                  } else {
                      if (DBG) logd("MmiCode 2: switchWaitingOrHoldingAndActive");
                      mCT.switchWaitingOrHoldingAndActive();
                  }
              } catch (CallStateException e) {
                  if (DBG) Rlog.d(LOG_TAG, "switch failed", e);
                  notifySuppServiceFailed(Phone.SuppService.SWITCH);
              }
          }
  
          return true;
      }
  
      private boolean handleMultipartyIncallSupplementaryService(String dialString) {
          if (dialString.length() > 1) {
              return false;
          }
  
          if (DBG) logd("MmiCode 3: merge calls");
          conference();
          return true;
      }
  
      private boolean handleEctIncallSupplementaryService(String dialString) {
  
          int len = dialString.length();
  
          if (len != 1) {
              return false;
          }
  
          if (DBG) logd("MmiCode 4: explicit call transfer");
          explicitCallTransfer();
          return true;
      }
  
      private boolean handleCcbsIncallSupplementaryService(String dialString) {
          if (dialString.length() > 1) {
              return false;
          }
  
          Rlog.i(LOG_TAG, "MmiCode 5: CCBS not supported!");
          // Treat it as an "unknown" service.
          notifySuppServiceFailed(Phone.SuppService.UNKNOWN);
          return true;
      }
  
      @UnsupportedAppUsage
      @Override
      public boolean handleInCallMmiCommands(String dialString) throws CallStateException {
          if (!isPhoneTypeGsm()) {
              loge("method handleInCallMmiCommands is NOT supported in CDMA!");
              return false;
          }
  
          Phone imsPhone = mImsPhone;
          if (imsPhone != null
                  && imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE) {
              return imsPhone.handleInCallMmiCommands(dialString);
          }
  
          if (!isInCall()) {
              return false;
          }
  
          if (TextUtils.isEmpty(dialString)) {
              return false;
          }
  
          boolean result = false;
          char ch = dialString.charAt(0);
          switch (ch) {
              case '0':
                  result = handleCallDeflectionIncallSupplementaryService(dialString);
                  break;
              case '1':
                  result = handleCallWaitingIncallSupplementaryService(dialString);
                  break;
              case '2':
                  result = handleCallHoldIncallSupplementaryService(dialString);
                  break;
              case '3':
                  result = handleMultipartyIncallSupplementaryService(dialString);
                  break;
              case '4':
                  result = handleEctIncallSupplementaryService(dialString);
                  break;
              case '5':
                  result = handleCcbsIncallSupplementaryService(dialString);
                  break;
              default:
                  break;
          }
  
          return result;
      }
  
      @UnsupportedAppUsage
      public boolean isInCall() {
          GsmCdmaCall.State foregroundCallState = getForegroundCall().getState();
          GsmCdmaCall.State backgroundCallState = getBackgroundCall().getState();
          GsmCdmaCall.State ringingCallState = getRingingCall().getState();
  
         return (foregroundCallState.isAlive() ||
                  backgroundCallState.isAlive() ||
                  ringingCallState.isAlive());
      }
  
      @Override
      public Connection dial(String dialString, @NonNull DialArgs dialArgs)
              throws CallStateException {
          if (!isPhoneTypeGsm() && dialArgs.uusInfo != null) {
              throw new CallStateException("Sending UUS information NOT supported in CDMA!");
          }
  
          boolean isEmergency = PhoneNumberUtils.isEmergencyNumber(getSubId(), dialString);
          Phone imsPhone = mImsPhone;
  
          CarrierConfigManager configManager =
                  (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
          boolean alwaysTryImsForEmergencyCarrierConfig = configManager.getConfigForSubId(getSubId())
                  .getBoolean(CarrierConfigManager.KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL);
  
          /** Check if the call is Wireless Priority Service call */
          boolean isWpsCall = dialString != null ? dialString.startsWith(PREFIX_WPS) : false;
          boolean allowWpsOverIms = configManager.getConfigForSubId(getSubId())
                  .getBoolean(CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL);
  
          boolean useImsForCall = isImsUseEnabled()
                   && imsPhone != null
                   && (imsPhone.isVolteEnabled() || imsPhone.isWifiCallingEnabled() ||
                   (imsPhone.isVideoEnabled() && VideoProfile.isVideo(dialArgs.videoState)))
                   && (imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
                   && (isWpsCall ? allowWpsOverIms : true);
  
          boolean useImsForEmergency = imsPhone != null
                  && isEmergency
                  && alwaysTryImsForEmergencyCarrierConfig
                  && ImsManager.getInstance(mContext, mPhoneId).isNonTtyOrTtyOnVolteEnabled()
                  && imsPhone.isImsAvailable();
  
          String dialPart = PhoneNumberUtils.extractNetworkPortionAlt(PhoneNumberUtils.
                  stripSeparators(dialString));
          boolean isUt = (dialPart.startsWith("*") || dialPart.startsWith("#"))
                  && dialPart.endsWith("#");
  
          boolean useImsForUt = imsPhone != null && imsPhone.isUtEnabled();
  
          if (DBG) {
              logd("useImsForCall=" + useImsForCall
                      + ", useImsForEmergency=" + useImsForEmergency
                      + ", useImsForUt=" + useImsForUt
                      + ", isUt=" + isUt
                      + ", isWpsCall=" + isWpsCall
                      + ", allowWpsOverIms=" + allowWpsOverIms
                      + ", imsPhone=" + imsPhone
                      + ", imsPhone.isVolteEnabled()="
                      + ((imsPhone != null) ? imsPhone.isVolteEnabled() : "N/A")
                      + ", imsPhone.isVowifiEnabled()="
                      + ((imsPhone != null) ? imsPhone.isWifiCallingEnabled() : "N/A")
                      + ", imsPhone.isVideoEnabled()="
                      + ((imsPhone != null) ? imsPhone.isVideoEnabled() : "N/A")
                      + ", imsPhone.getServiceState().getState()="
                      + ((imsPhone != null) ? imsPhone.getServiceState().getState() : "N/A"));
          }
  
          Phone.checkWfcWifiOnlyModeBeforeDial(mImsPhone, mPhoneId, mContext);
  
          if ((useImsForCall && !isUt) || (isUt && useImsForUt) || useImsForEmergency) {
              try {
                  if (DBG) logd("Trying IMS PS call");
                  return imsPhone.dial(dialString, dialArgs);
              } catch (CallStateException e) {
                  if (DBG) logd("IMS PS call exception " + e +
                          "useImsForCall =" + useImsForCall + ", imsPhone =" + imsPhone);
                  // Do not throw a CallStateException and instead fall back to Circuit switch
                  // for emergency calls and MMI codes.
                  if (Phone.CS_FALLBACK.equals(e.getMessage()) || isEmergency) {
                      logi("IMS call failed with Exception: " + e.getMessage() + ". Falling back "
                              + "to CS.");
                  } else {
                      CallStateException ce = new CallStateException(e.getError(), e.getMessage());
                      ce.setStackTrace(e.getStackTrace());
                      throw ce;
                  }
              }
          }
  
          if (mSST != null && mSST.mSS.getState() == ServiceState.STATE_OUT_OF_SERVICE
                  && mSST.mSS.getDataRegState() != ServiceState.STATE_IN_SERVICE && !isEmergency) {
              throw new CallStateException("cannot dial in current state");
          }
          // Check non-emergency voice CS call - shouldn't dial when POWER_OFF
          if (mSST != null && mSST.mSS.getState() == ServiceState.STATE_POWER_OFF /* CS POWER_OFF */
                  && !VideoProfile.isVideo(dialArgs.videoState) /* voice call */
                  && !isEmergency /* non-emergency call */
                  && !(isUt && useImsForUt) /* not UT */) {
              throw new CallStateException(
                  CallStateException.ERROR_POWER_OFF,
                  "cannot dial voice call in airplane mode");
          }
          // Check for service before placing non emergency CS voice call.
          // Allow dial only if either CS is camped on any RAT (or) PS is in LTE service.
          if (mSST != null
                  && mSST.mSS.getState() == ServiceState.STATE_OUT_OF_SERVICE /* CS out of service */
                  && !(mSST.mSS.getDataRegState() == ServiceState.STATE_IN_SERVICE
                      && ServiceState.isLte(mSST.mSS.getRilDataRadioTechnology())) /* PS not in LTE */
                  && !VideoProfile.isVideo(dialArgs.videoState) /* voice call */
                  && !isEmergency /* non-emergency call */) {
              throw new CallStateException(
                  CallStateException.ERROR_OUT_OF_SERVICE,
                  "cannot dial voice call in out of service");
          }
          if (DBG) logd("Trying (non-IMS) CS call");
  
          if (isPhoneTypeGsm()) {
              return dialInternal(dialString, new DialArgs.Builder<>()
                      .setIntentExtras(dialArgs.intentExtras)
                      .build());
          } else {
              return dialInternal(dialString, dialArgs);
          }
      }
  
      /**
       * @return {@code true} if the user should be informed of an attempt to dial an international
       * number while on WFC only, {@code false} otherwise.
       */
      public boolean isNotificationOfWfcCallRequired(String dialString) {
          CarrierConfigManager configManager =
                  (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
          PersistableBundle config = configManager.getConfigForSubId(getSubId());
  
          // Determine if carrier config indicates that international calls over WFC should trigger a
          // notification to the user. This is controlled by carrier configuration and is off by
          // default.
          boolean shouldNotifyInternationalCallOnWfc = config != null
                  && config.getBoolean(
                          CarrierConfigManager.KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL);
  
          if (!shouldNotifyInternationalCallOnWfc) {
              return false;
          }
  
          Phone imsPhone = mImsPhone;
          boolean isEmergency = PhoneNumberUtils.isEmergencyNumber(getSubId(), dialString);
          boolean shouldConfirmCall =
                          // Using IMS
                          isImsUseEnabled()
                          && imsPhone != null
                          // VoLTE not available
                          && !imsPhone.isVolteEnabled()
                          // WFC is available
                          && imsPhone.isWifiCallingEnabled()
                          && !isEmergency
                          // Dialing international number
                          && PhoneNumberUtils.isInternationalNumber(dialString, getCountryIso());
          return shouldConfirmCall;
      }
  
      @Override
      protected Connection dialInternal(String dialString, DialArgs dialArgs)
              throws CallStateException {
          return dialInternal(dialString, dialArgs, null);
      }
  
      protected Connection dialInternal(String dialString, DialArgs dialArgs,
              ResultReceiver wrappedCallback)
              throws CallStateException {
  
          // Need to make sure dialString gets parsed properly
          String newDialString = PhoneNumberUtils.stripSeparators(dialString);
  
          if (isPhoneTypeGsm()) {
              // handle in-call MMI first if applicable
              if (handleInCallMmiCommands(newDialString)) {
                  return null;
              }
  
              // Only look at the Network portion for mmi
              String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString);
              GsmMmiCode mmi = GsmMmiCode.newFromDialString(networkPortion, this,
                      mUiccApplication.get(), wrappedCallback);
              if (DBG) logd("dialInternal: dialing w/ mmi '" + mmi + "'...");
  
              if (mmi == null) {
                  return mCT.dialGsm(newDialString, dialArgs.uusInfo, dialArgs.intentExtras);
              } else if (mmi.isTemporaryModeCLIR()) {
                  return mCT.dialGsm(mmi.mDialingNumber, mmi.getCLIRMode(), dialArgs.uusInfo,
                          dialArgs.intentExtras);
              } else {
                  mPendingMMIs.add(mmi);
                  mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
                  mmi.processCode();
                  return null;
              }
          } else {
              return mCT.dial(newDialString, dialArgs.intentExtras);
          }
      }
  
     @Override
      public boolean handlePinMmi(String dialString) {
          MmiCode mmi;
          if (isPhoneTypeGsm()) {
              mmi = GsmMmiCode.newFromDialString(dialString, this, mUiccApplication.get());
          } else {
              mmi = CdmaMmiCode.newFromDialString(dialString, this, mUiccApplication.get());
          }
  
          if (mmi != null && mmi.isPinPukCommand()) {
              mPendingMMIs.add(mmi);
              mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
              try {
                  mmi.processCode();
              } catch (CallStateException e) {
                  //do nothing
              }
              return true;
          }
  
          loge("Mmi is null or unrecognized!");
          return false;
      }
  
      private void sendUssdResponse(String ussdRequest, CharSequence message, int returnCode,
                                     ResultReceiver wrappedCallback) {
          UssdResponse response = new UssdResponse(ussdRequest, message);
          Bundle returnData = new Bundle();
          returnData.putParcelable(TelephonyManager.USSD_RESPONSE, response);
          wrappedCallback.send(returnCode, returnData);
      }
  
      @Override
      public boolean handleUssdRequest(String ussdRequest, ResultReceiver wrappedCallback) {
          if (!isPhoneTypeGsm() || mPendingMMIs.size() > 0) {
              //todo: replace the generic failure with specific error code.
              sendUssdResponse(ussdRequest, null, TelephonyManager.USSD_RETURN_FAILURE,
                      wrappedCallback );
              return true;
          }
  
          // Try over IMS if possible.
          Phone imsPhone = mImsPhone;
          if ((imsPhone != null)
                  && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
                  || imsPhone.isUtEnabled())) {
              try {
                  logd("handleUssdRequest: attempting over IMS");
                  return imsPhone.handleUssdRequest(ussdRequest, wrappedCallback);
              } catch (CallStateException cse) {
                  if (!CS_FALLBACK.equals(cse.getMessage())) {
                      return false;
                  }
                  // At this point we've tried over IMS but have been informed we need to handover
                  // back to GSM.
                  logd("handleUssdRequest: fallback to CS required");
              }
          }
  
          // Try USSD over GSM.
          try {
              dialInternal(ussdRequest, new DialArgs.Builder<>().build(), wrappedCallback);
          } catch (Exception e) {
              logd("handleUssdRequest: exception" + e);
              return false;
          }
          return true;
      }
  
      @Override
      public void sendUssdResponse(String ussdMessge) {
          if (isPhoneTypeGsm()) {
              GsmMmiCode mmi = GsmMmiCode.newFromUssdUserInput(ussdMessge, this, mUiccApplication.get());
              mPendingMMIs.add(mmi);
              mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
              mmi.sendUssd(ussdMessge);
          } else {
              loge("sendUssdResponse: not possible in CDMA");
          }
      }
  
      @Override
      public void sendDtmf(char c) {
          if (!PhoneNumberUtils.is12Key(c)) {
              loge("sendDtmf called with invalid character '" + c + "'");
          } else {
              if (mCT.mState ==  PhoneConstants.State.OFFHOOK) {
                  mCi.sendDtmf(c, null);
              }
          }
      }
  
      @Override
      public void startDtmf(char c) {
          if (!PhoneNumberUtils.is12Key(c)) {
              loge("startDtmf called with invalid character '" + c + "'");
          } else {
              mCi.startDtmf(c, null);
          }
      }
  
      @Override
      public void stopDtmf() {
          mCi.stopDtmf(null);
      }
  
      @Override
      public void sendBurstDtmf(String dtmfString, int on, int off, Message onComplete) {
          if (isPhoneTypeGsm()) {
              loge("[GsmCdmaPhone] sendBurstDtmf() is a CDMA method");
          } else {
              boolean check = true;
              for (int itr = 0;itr < dtmfString.length(); itr++) {
                  if (!PhoneNumberUtils.is12Key(dtmfString.charAt(itr))) {
                      Rlog.e(LOG_TAG,
                              "sendDtmf called with invalid character '" + dtmfString.charAt(itr)+ "'");
                      check = false;
                      break;
                  }
              }
              if (mCT.mState == PhoneConstants.State.OFFHOOK && check) {
                  mCi.sendBurstDtmf(dtmfString, on, off, onComplete);
              }
          }
      }
  
      @Override
      public void setRadioPower(boolean power) {
          mSST.setRadioPower(power);
      }
  
      private void storeVoiceMailNumber(String number) {
          SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
          SharedPreferences.Editor editor = sp.edit();
          setVmSimImsi(getSubscriberId());
          logd("storeVoiceMailNumber: mPrecisePhoneType=" + mPrecisePhoneType + " vmNumber="
                  + number);
          if (isPhoneTypeGsm()) {
              editor.putString(VM_NUMBER + getPhoneId(), number);
              editor.apply();
          } else {
              editor.putString(VM_NUMBER_CDMA + getPhoneId(), number);
              editor.apply();
          }
      }
  
      @Override
      public String getVoiceMailNumber() {
          String number = null;
          if (isPhoneTypeGsm() || mSimRecords != null) {
              // Read from the SIM. If its null, try reading from the shared preference area.
              IccRecords r = isPhoneTypeGsm() ? mIccRecords.get() : mSimRecords;
              number = (r != null) ? r.getVoiceMailNumber() : "";
              if (TextUtils.isEmpty(number)) {
                  SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
                  String spName = isPhoneTypeGsm() ? VM_NUMBER : VM_NUMBER_CDMA;
                  number = sp.getString(spName + getPhoneId(), null);
                  logd("getVoiceMailNumber: from " + spName + " number=" + number);
              } else {
                  logd("getVoiceMailNumber: from IccRecords number=" + number);
              }
          }
          if (!isPhoneTypeGsm() && TextUtils.isEmpty(number)) {
              SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
              number = sp.getString(VM_NUMBER_CDMA + getPhoneId(), null);
              logd("getVoiceMailNumber: from VM_NUMBER_CDMA number=" + number);
          }
  
          if (TextUtils.isEmpty(number)) {
              CarrierConfigManager configManager = (CarrierConfigManager)
                      getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
              PersistableBundle b = configManager.getConfigForSubId(getSubId());
              if (b != null) {
                  String defaultVmNumber =
                          b.getString(CarrierConfigManager.KEY_DEFAULT_VM_NUMBER_STRING);
                  String defaultVmNumberRoaming =
                          b.getString(CarrierConfigManager.KEY_DEFAULT_VM_NUMBER_ROAMING_STRING);
                  if (!TextUtils.isEmpty(defaultVmNumberRoaming) && mSST.mSS.getRoaming()) {
                      number = defaultVmNumberRoaming;
                  } else if (!TextUtils.isEmpty(defaultVmNumber)) {
                      number = defaultVmNumber;
                  }
              }
          }
  
          if (TextUtils.isEmpty(number)) {
              // Read platform settings for dynamic voicemail number
              CarrierConfigManager configManager = (CarrierConfigManager)
                      getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
              PersistableBundle b = configManager.getConfigForSubId(getSubId());
              if (b != null && b.getBoolean(
                      CarrierConfigManager.KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL)) {
                  number = getLine1Number();
              }
          }
  
          return number;
      }
  
      private String getVmSimImsi() {
          SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
          return sp.getString(VM_SIM_IMSI + getPhoneId(), null);
      }
  
      private void setVmSimImsi(String imsi) {
          SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
          SharedPreferences.Editor editor = sp.edit();
          editor.putString(VM_SIM_IMSI + getPhoneId(), imsi);
          editor.apply();
      }
  
      @Override
      public String getVoiceMailAlphaTag() {
          String ret = "";
  
          if (isPhoneTypeGsm()) {
              IccRecords r = mIccRecords.get();
  
              ret = (r != null) ? r.getVoiceMailAlphaTag() : "";
          }
  
          if (ret == null || ret.length() == 0) {
              return mContext.getText(
                  com.android.internal.R.string.defaultVoiceMailAlphaTag).toString();
          }
  
          return ret;
      }
  
      @Override
      public String getDeviceId() {
          if (isPhoneTypeGsm()) {
              return mImei;
          } else {
              CarrierConfigManager configManager = (CarrierConfigManager)
                      mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
              boolean force_imei = configManager.getConfigForSubId(getSubId())
                      .getBoolean(CarrierConfigManager.KEY_FORCE_IMEI_BOOL);
              if (force_imei) return mImei;
  
              String id = getMeid();
              if ((id == null) || id.matches("^0*$")) {
                  loge("getDeviceId(): MEID is not initialized use ESN");
                  id = getEsn();
              }
              return id;
          }
      }
  
      @Override
      public String getDeviceSvn() {
          if (isPhoneTypeGsm() || isPhoneTypeCdmaLte()) {
              return mImeiSv;
          } else {
              loge("getDeviceSvn(): return 0");
              return "0";
          }
      }
  
      @Override
      public IsimRecords getIsimRecords() {
          return mIsimUiccRecords;
      }
  
      @Override
      public String getImei() {
          return mImei;
      }
  
      @UnsupportedAppUsage
      @Override
      public String getEsn() {
          if (isPhoneTypeGsm()) {
              loge("[GsmCdmaPhone] getEsn() is a CDMA method");
              return "0";
          } else {
              return mEsn;
          }
      }
  
      @Override
      public String getMeid() {
          return mMeid;
      }
  
      @Override
      public String getNai() {
          IccRecords r = mUiccController.getIccRecords(mPhoneId, UiccController.APP_FAM_3GPP2);
          if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
              Rlog.v(LOG_TAG, "IccRecords is " + r);
          }
          return (r != null) ? r.getNAI() : null;
      }
  
      @Override
      @Nullable
      public String getSubscriberId() {
          String subscriberId = null;
          if (isPhoneTypeCdma()) {
              subscriberId = mSST.getImsi();
          } else {
              // Both Gsm and CdmaLte get the IMSI from Usim.
              IccRecords iccRecords = mUiccController.getIccRecords(
                      mPhoneId, UiccController.APP_FAM_3GPP);
              if (iccRecords != null) {
                  subscriberId = iccRecords.getIMSI();
              }
          }
          return subscriberId;
      }
  
      @Override
      public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType) {
          return CarrierInfoManager.getCarrierInfoForImsiEncryption(keyType, mContext);
      }
  
      @Override
      public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo) {
          CarrierInfoManager.setCarrierInfoForImsiEncryption(imsiEncryptionInfo, mContext, mPhoneId);
      }
  
      @Override
      public int getCarrierId() {
          return mCarrierResolver.getCarrierId();
      }
  
      @Override
      public String getCarrierName() {
          return mCarrierResolver.getCarrierName();
      }
  
      @Override
      public int getMNOCarrierId() {
          return mCarrierResolver.getMnoCarrierId();
      }
  
      @Override
      public int getSpecificCarrierId() {
          return mCarrierResolver.getSpecificCarrierId();
      }
  
      @Override
      public String getSpecificCarrierName() {
          return mCarrierResolver.getSpecificCarrierName();
      }
  
      @Override
      public void resolveSubscriptionCarrierId(String simState) {
          mCarrierResolver.resolveSubscriptionCarrierId(simState);
      }
  
      @Override
      public int getCarrierIdListVersion() {
          return mCarrierResolver.getCarrierListVersion();
      }
  
      @Override
      public void resetCarrierKeysForImsiEncryption() {
          mCIM.resetCarrierKeysForImsiEncryption(mContext, mPhoneId);
      }
  
      @Override
      public void setCarrierTestOverride(String mccmnc, String imsi, String iccid, String gid1,
              String gid2, String pnn, String spn, String carrierPrivilegeRules, String apn) {
          mCarrierResolver.setTestOverrideApn(apn);
          mCarrierResolver.setTestOverrideCarrierPriviledgeRule(carrierPrivilegeRules);
          IccRecords r = null;
          if (isPhoneTypeGsm()) {
              r = mIccRecords.get();
          } else if (isPhoneTypeCdmaLte()) {
              r = mSimRecords;
          } else {
              loge("setCarrierTestOverride fails in CDMA only");
          }
          if (r != null) {
              r.setCarrierTestOverride(mccmnc, imsi, iccid, gid1, gid2, pnn, spn);
          }
      }
  
      @Override
      public String getGroupIdLevel1() {
          if (isPhoneTypeGsm()) {
              IccRecords r = mIccRecords.get();
              return (r != null) ? r.getGid1() : null;
          } else if (isPhoneTypeCdma()) {
              loge("GID1 is not available in CDMA");
              return null;
          } else { //isPhoneTypeCdmaLte()
              return (mSimRecords != null) ? mSimRecords.getGid1() : "";
          }
      }
  
      @Override
      public String getGroupIdLevel2() {
          if (isPhoneTypeGsm()) {
              IccRecords r = mIccRecords.get();
              return (r != null) ? r.getGid2() : null;
          } else if (isPhoneTypeCdma()) {
              loge("GID2 is not available in CDMA");
              return null;
          } else { //isPhoneTypeCdmaLte()
              return (mSimRecords != null) ? mSimRecords.getGid2() : "";
          }
      }
  
      @UnsupportedAppUsage
      @Override
      public String getLine1Number() {
          if (isPhoneTypeGsm()) {
              IccRecords r = mIccRecords.get();
              return (r != null) ? r.getMsisdnNumber() : null;
          } else {
              CarrierConfigManager configManager = (CarrierConfigManager)
                      mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
              boolean use_usim = configManager.getConfigForSubId(getSubId()).getBoolean(
                      CarrierConfigManager.KEY_USE_USIM_BOOL);
              if (use_usim) {
                  return (mSimRecords != null) ? mSimRecords.getMsisdnNumber() : null;
              }
              return mSST.getMdnNumber();
          }
      }
  
      @Override
      public String getPlmn() {
          if (isPhoneTypeGsm()) {
              IccRecords r = mIccRecords.get();
              return (r != null) ? r.getPnnHomeName() : null;
          } else if (isPhoneTypeCdma()) {
              loge("Plmn is not available in CDMA");
              return null;
          } else { //isPhoneTypeCdmaLte()
              return (mSimRecords != null) ? mSimRecords.getPnnHomeName() : null;
          }
      }
  
      @Override
      public String getCdmaPrlVersion() {
          return mSST.getPrlVersion();
      }
  
      @Override
      public String getCdmaMin() {
          return mSST.getCdmaMin();
      }
  
      @Override
      public boolean isMinInfoReady() {
          return mSST.isMinInfoReady();
      }
  
      @Override
      public String getMsisdn() {
          if (isPhoneTypeGsm()) {
              IccRecords r = mIccRecords.get();
              return (r != null) ? r.getMsisdnNumber() : null;
          } else if (isPhoneTypeCdmaLte()) {
              return (mSimRecords != null) ? mSimRecords.getMsisdnNumber() : null;
          } else {
              loge("getMsisdn: not expected on CDMA");
              return null;
          }
      }
  
      @Override
      public String getLine1AlphaTag() {
          if (isPhoneTypeGsm()) {
              IccRecords r = mIccRecords.get();
              return (r != null) ? r.getMsisdnAlphaTag() : null;
          } else {
              loge("getLine1AlphaTag: not possible in CDMA");
              return null;
          }
      }
  
      @Override
      public boolean setLine1Number(String alphaTag, String number, Message onComplete) {
          if (isPhoneTypeGsm()) {
              IccRecords r = mIccRecords.get();
              if (r != null) {
                  r.setMsisdnNumber(alphaTag, number, onComplete);
                  return true;
              } else {
                  return false;
              }
          } else {
              loge("setLine1Number: not possible in CDMA");
              return false;
          }
      }
  
      @Override
      public void setVoiceMailNumber(String alphaTag, String voiceMailNumber, Message onComplete) {
          Message resp;
          mVmNumber = voiceMailNumber;
          resp = obtainMessage(EVENT_SET_VM_NUMBER_DONE, 0, 0, onComplete);
  
          IccRecords r = mIccRecords.get();
  
          if (!isPhoneTypeGsm() && mSimRecords != null) {
              r = mSimRecords;
          }
  
          if (r != null) {
              r.setVoiceMailNumber(alphaTag, mVmNumber, resp);
          }
      }
  
      @UnsupportedAppUsage
      private boolean isValidCommandInterfaceCFReason (int commandInterfaceCFReason) {
          switch (commandInterfaceCFReason) {
              case CF_REASON_UNCONDITIONAL:
              case CF_REASON_BUSY:
              case CF_REASON_NO_REPLY:
              case CF_REASON_NOT_REACHABLE:
              case CF_REASON_ALL:
              case CF_REASON_ALL_CONDITIONAL:
                  return true;
              default:
                  return false;
          }
      }
  
      @UnsupportedAppUsage
      @Override
      public String getSystemProperty(String property, String defValue) {
          if (getUnitTestMode()) {
              return null;
          }
          return TelephonyManager.getTelephonyProperty(mPhoneId, property, defValue);
      }
  
      @UnsupportedAppUsage
      private boolean isValidCommandInterfaceCFAction (int commandInterfaceCFAction) {
          switch (commandInterfaceCFAction) {
              case CF_ACTION_DISABLE:
              case CF_ACTION_ENABLE:
              case CF_ACTION_REGISTRATION:
              case CF_ACTION_ERASURE:
                  return true;
              default:
                  return false;
          }
      }
  
      @UnsupportedAppUsage
      private boolean isCfEnable(int action) {
          return (action == CF_ACTION_ENABLE) || (action == CF_ACTION_REGISTRATION);
      }
  
      private boolean isImsUtEnabledOverCdma() {
          return isPhoneTypeCdmaLte()
              && mImsPhone != null
              && mImsPhone.isUtEnabled();
      }
  
      @Override
      public void getCallForwardingOption(int commandInterfaceCFReason, Message onComplete) {
          if (isPhoneTypeGsm() || isImsUtEnabledOverCdma()) {
              Phone imsPhone = mImsPhone;
              if ((imsPhone != null)
                      && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
                      || imsPhone.isUtEnabled())) {
                  imsPhone.getCallForwardingOption(commandInterfaceCFReason, onComplete);
                  return;
              }
  
              if (isValidCommandInterfaceCFReason(commandInterfaceCFReason)) {
                  if (DBG) logd("requesting call forwarding query.");
                  Message resp;
                  if (commandInterfaceCFReason == CF_REASON_UNCONDITIONAL) {
                      resp = obtainMessage(EVENT_GET_CALL_FORWARD_DONE, onComplete);
                  } else {
                      resp = onComplete;
                  }
                  mCi.queryCallForwardStatus(commandInterfaceCFReason,
                          CommandsInterface.SERVICE_CLASS_VOICE, null, resp);
              }
          } else {
              loge("getCallForwardingOption: not possible in CDMA without IMS");
          }
      }
  
      @Override
      public void setCallForwardingOption(int commandInterfaceCFAction,
              int commandInterfaceCFReason,
              String dialingNumber,
              int timerSeconds,
              Message onComplete) {
          if (isPhoneTypeGsm() || isImsUtEnabledOverCdma()) {
              Phone imsPhone = mImsPhone;
              if ((imsPhone != null)
                      && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
                      || imsPhone.isUtEnabled())) {
                  imsPhone.setCallForwardingOption(commandInterfaceCFAction,
                          commandInterfaceCFReason, dialingNumber, timerSeconds, onComplete);
                  return;
              }
  
              if ((isValidCommandInterfaceCFAction(commandInterfaceCFAction)) &&
                      (isValidCommandInterfaceCFReason(commandInterfaceCFReason))) {
  
                  Message resp;
                  if (commandInterfaceCFReason == CF_REASON_UNCONDITIONAL) {
                      Cfu cfu = new Cfu(dialingNumber, onComplete);
                      resp = obtainMessage(EVENT_SET_CALL_FORWARD_DONE,
                              isCfEnable(commandInterfaceCFAction) ? 1 : 0, 0, cfu);
                  } else {
                      resp = onComplete;
                  }
                  mCi.setCallForward(commandInterfaceCFAction,
                          commandInterfaceCFReason,
                          CommandsInterface.SERVICE_CLASS_VOICE,
                          dialingNumber,
                          timerSeconds,
                          resp);
              }
          } else {
              loge("setCallForwardingOption: not possible in CDMA without IMS");
          }
      }
  
      @Override
      public void getCallBarring(String facility, String password, Message onComplete,
              int serviceClass) {
          if (isPhoneTypeGsm()) {
              Phone imsPhone = mImsPhone;
              if ((imsPhone != null) && imsPhone.isUtEnabled()) {
                  imsPhone.getCallBarring(facility, password, onComplete, serviceClass);
                  return;
              }
              mCi.queryFacilityLock(facility, password, serviceClass, onComplete);
          } else {
              loge("getCallBarringOption: not possible in CDMA");
          }
      }
  
      @Override
      public void setCallBarring(String facility, boolean lockState, String password,
              Message onComplete, int serviceClass) {
          if (isPhoneTypeGsm()) {
              Phone imsPhone = mImsPhone;
              if ((imsPhone != null) && imsPhone.isUtEnabled()) {
                  imsPhone.setCallBarring(facility, lockState, password, onComplete, serviceClass);
                  return;
              }
              mCi.setFacilityLock(facility, lockState, password, serviceClass, onComplete);
          } else {
              loge("setCallBarringOption: not possible in CDMA");
          }
      }
  
      /**
       * Changes access code used for call barring
       *
       * @param facility is one of CB_FACILTY_*
       * @param oldPwd is old password
       * @param newPwd is new password
       * @param onComplete is callback message when the action is completed.
       */
      public void changeCallBarringPassword(String facility, String oldPwd, String newPwd,
              Message onComplete) {
          if (isPhoneTypeGsm()) {
              mCi.changeBarringPassword(facility, oldPwd, newPwd, onComplete);
          } else {
              loge("changeCallBarringPassword: not possible in CDMA");
          }
      }
  
      @Override
      public void getOutgoingCallerIdDisplay(Message onComplete) {
          if (isPhoneTypeGsm()) {
              Phone imsPhone = mImsPhone;
              if ((imsPhone != null)
                      && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
                      || imsPhone.isUtEnabled())) {
                  imsPhone.getOutgoingCallerIdDisplay(onComplete);
                  return;
              }
              mCi.getCLIR(onComplete);
          } else {
              loge("getOutgoingCallerIdDisplay: not possible in CDMA");
          }
      }
  
      @Override
      public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode, Message onComplete) {
          if (isPhoneTypeGsm()) {
              Phone imsPhone = mImsPhone;
              if ((imsPhone != null)
                      && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
                      || imsPhone.isUtEnabled())) {
                  imsPhone.setOutgoingCallerIdDisplay(commandInterfaceCLIRMode, onComplete);
                  return;
              }
              // Packing CLIR value in the message. This will be required for
              // SharedPreference caching, if the message comes back as part of
              // a success response.
              mCi.setCLIR(commandInterfaceCLIRMode,
                      obtainMessage(EVENT_SET_CLIR_COMPLETE, commandInterfaceCLIRMode, 0, onComplete));
          } else {
              loge("setOutgoingCallerIdDisplay: not possible in CDMA");
          }
      }
  
      @Override
      public void getCallWaiting(Message onComplete) {
          if (isPhoneTypeGsm() || isImsUtEnabledOverCdma()) {
              Phone imsPhone = mImsPhone;
              if ((imsPhone != null)
                      && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
                      || imsPhone.isUtEnabled())) {
                  imsPhone.getCallWaiting(onComplete);
                  return;
              }
  
              //As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service
              //class parameter in call waiting interrogation  to network
              mCi.queryCallWaiting(CommandsInterface.SERVICE_CLASS_NONE, onComplete);
          } else {
              mCi.queryCallWaiting(CommandsInterface.SERVICE_CLASS_VOICE, onComplete);
          }
      }
  
      @Override
      public void setCallWaiting(boolean enable, Message onComplete) {
          if (isPhoneTypeGsm() || isImsUtEnabledOverCdma()) {
              Phone imsPhone = mImsPhone;
              if ((imsPhone != null)
                      && ((imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE)
                      || imsPhone.isUtEnabled())) {
                  imsPhone.setCallWaiting(enable, onComplete);
                  return;
              }
              int serviceClass = CommandsInterface.SERVICE_CLASS_VOICE;
              CarrierConfigManager configManager = (CarrierConfigManager)
                      getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
              PersistableBundle b = configManager.getConfigForSubId(getSubId());
              if (b != null) {
                  serviceClass = b.getInt(CarrierConfigManager.KEY_CALL_WAITING_SERVICE_CLASS_INT,
                          CommandsInterface.SERVICE_CLASS_VOICE);
              }
              mCi.setCallWaiting(enable, serviceClass, onComplete);
          } else {
              loge("method setCallWaiting is NOT supported in CDMA without IMS!");
          }
      }
  
      @Override
      public void getAvailableNetworks(Message response) {
          if (isPhoneTypeGsm() || isPhoneTypeCdmaLte()) {
              Message msg = obtainMessage(EVENT_GET_AVAILABLE_NETWORKS_DONE, response);
              mCi.getAvailableNetworks(msg);
          } else {
              loge("getAvailableNetworks: not possible in CDMA");
          }
      }
  
      @Override
      public void startNetworkScan(NetworkScanRequest nsr, Message response) {
          mCi.startNetworkScan(nsr, response);
      }
  
      @Override
      public void stopNetworkScan(Message response) {
          mCi.stopNetworkScan(response);
      }
  
      @Override
      public void setTTYMode(int ttyMode, Message onComplete) {
          // Send out the TTY Mode change over RIL as well
          super.setTTYMode(ttyMode, onComplete);
          if (mImsPhone != null) {
              mImsPhone.setTTYMode(ttyMode, onComplete);
          }
      }
  
      @Override
      public void setUiTTYMode(int uiTtyMode, Message onComplete) {
         if (mImsPhone != null) {
             mImsPhone.setUiTTYMode(uiTtyMode, onComplete);
         }
      }
  
      @Override
      public void setMute(boolean muted) {
          mCT.setMute(muted);
      }
  
      @Override
      public boolean getMute() {
          return mCT.getMute();
      }
  
      @Override
      public void updateServiceLocation() {
          mSST.enableSingleLocationUpdate();
      }
  
      @Override
      public void enableLocationUpdates() {
          mSST.enableLocationUpdates();
      }
  
      @Override
      public void disableLocationUpdates() {
          mSST.disableLocationUpdates();
      }
  
      @Override
      public boolean getDataRoamingEnabled() {
          if (getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) != null) {
              return getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).getDataRoamingEnabled();
          }
          return false;
      }
  
      @Override
      public void setDataRoamingEnabled(boolean enable) {
          if (getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) != null) {
              getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                      .setDataRoamingEnabledByUser(enable);
          }
      }
  
      @Override
      public void registerForCdmaOtaStatusChange(Handler h, int what, Object obj) {
          mCi.registerForCdmaOtaProvision(h, what, obj);
      }
  
      @Override
      public void unregisterForCdmaOtaStatusChange(Handler h) {
          mCi.unregisterForCdmaOtaProvision(h);
      }
  
      @Override
      public void registerForSubscriptionInfoReady(Handler h, int what, Object obj) {
          mSST.registerForSubscriptionInfoReady(h, what, obj);
      }
  
      @Override
      public void unregisterForSubscriptionInfoReady(Handler h) {
          mSST.unregisterForSubscriptionInfoReady(h);
      }
  
      @UnsupportedAppUsage
      @Override
      public void setOnEcbModeExitResponse(Handler h, int what, Object obj) {
          mEcmExitRespRegistrant = new Registrant(h, what, obj);
      }
  
      @Override
      public void unsetOnEcbModeExitResponse(Handler h) {
          mEcmExitRespRegistrant.clear();
      }
  
      @Override
      public void registerForCallWaiting(Handler h, int what, Object obj) {
          mCT.registerForCallWaiting(h, what, obj);
      }
  
      @Override
      public void unregisterForCallWaiting(Handler h) {
          mCT.unregisterForCallWaiting(h);
      }
  
      /**
       * Whether data is enabled by user. Unlike isDataEnabled, this only
       * checks user setting stored in {@link android.provider.Settings.Global#MOBILE_DATA}
       * if not provisioning, or isProvisioningDataEnabled if provisioning.
       */
      @Override
      public boolean isUserDataEnabled() {
          if (mDataEnabledSettings.isProvisioning()) {
              return mDataEnabledSettings.isProvisioningDataEnabled();
          } else {
              return mDataEnabledSettings.isUserDataEnabled();
          }
      }
  
      /**
       * Removes the given MMI from the pending list and notifies
       * registrants that it is complete.
       * @param mmi MMI that is done
       */
      public void onMMIDone(MmiCode mmi) {
  
          /* Only notify complete if it's on the pending list.
           * Otherwise, it's already been handled (eg, previously canceled).
           * The exception is cancellation of an incoming USSD-REQUEST, which is
           * not on the list.
           */
          if (mPendingMMIs.remove(mmi) || (isPhoneTypeGsm() && (mmi.isUssdRequest() ||
                  ((GsmMmiCode)mmi).isSsInfo()))) {
  
              ResultReceiver receiverCallback = mmi.getUssdCallbackReceiver();
              if (receiverCallback != null) {
                  Rlog.i(LOG_TAG, "onMMIDone: invoking callback: " + mmi);
                  int returnCode = (mmi.getState() ==  MmiCode.State.COMPLETE) ?
                      TelephonyManager.USSD_RETURN_SUCCESS : TelephonyManager.USSD_RETURN_FAILURE;
                  sendUssdResponse(mmi.getDialString(), mmi.getMessage(), returnCode,
                          receiverCallback );
              } else {
                  Rlog.i(LOG_TAG, "onMMIDone: notifying registrants: " + mmi);
                  mMmiCompleteRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
              }
          } else {
              Rlog.i(LOG_TAG, "onMMIDone: invalid response or already handled; ignoring: " + mmi);
          }
      }
  
      public boolean supports3gppCallForwardingWhileRoaming() {
          CarrierConfigManager configManager = (CarrierConfigManager)
                  getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
          PersistableBundle b = configManager.getConfigForSubId(getSubId());
          if (b != null) {
              return b.getBoolean(
                      CarrierConfigManager.KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true);
          } else {
              // Default value set in CarrierConfigManager
              return true;
          }
      }
  
      private void onNetworkInitiatedUssd(MmiCode mmi) {
          Rlog.v(LOG_TAG, "onNetworkInitiatedUssd: mmi=" + mmi);
          mMmiCompleteRegistrants.notifyRegistrants(
              new AsyncResult(null, mmi, null));
      }
  
      /** ussdMode is one of CommandsInterface.USSD_MODE_* */
      private void onIncomingUSSD (int ussdMode, String ussdMessage) {
          if (!isPhoneTypeGsm()) {
              loge("onIncomingUSSD: not expected on GSM");
          }
          boolean isUssdError;
          boolean isUssdRequest;
          boolean isUssdRelease;
  
          isUssdRequest
              = (ussdMode == CommandsInterface.USSD_MODE_REQUEST);
  
          isUssdError
              = (ussdMode != CommandsInterface.USSD_MODE_NOTIFY
                  && ussdMode != CommandsInterface.USSD_MODE_REQUEST);
  
          isUssdRelease = (ussdMode == CommandsInterface.USSD_MODE_NW_RELEASE);
  
  
          // See comments in GsmMmiCode.java
          // USSD requests aren't finished until one
          // of these two events happen
          GsmMmiCode found = null;
          for (int i = 0, s = mPendingMMIs.size() ; i < s; i++) {
              if(((GsmMmiCode)mPendingMMIs.get(i)).isPendingUSSD()) {
                  found = (GsmMmiCode)mPendingMMIs.get(i);
                  break;
              }
          }
  
          if (found != null) {
              // Complete pending USSD
  
              if (isUssdRelease) {
                  found.onUssdRelease();
              } else if (isUssdError) {
                  found.onUssdFinishedError();
              } else {
                  found.onUssdFinished(ussdMessage, isUssdRequest);
              }
          } else if (!isUssdError && ussdMessage != null) {
              // pending USSD not found
              // The network may initiate its own USSD request
  
              // ignore everything that isnt a Notify or a Request
              // also, discard if there is no message to present
              GsmMmiCode mmi;
              mmi = GsmMmiCode.newNetworkInitiatedUssd(ussdMessage,
                                                     isUssdRequest,
                                                     GsmCdmaPhone.this,
                                                     mUiccApplication.get());
              onNetworkInitiatedUssd(mmi);
          }
      }
  
      /**
       * Make sure the network knows our preferred setting.
       */
      @UnsupportedAppUsage
      private void syncClirSetting() {
          SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
          int clirSetting = sp.getInt(CLIR_KEY + getPhoneId(), -1);
          Rlog.i(LOG_TAG, "syncClirSetting: " + CLIR_KEY + getPhoneId() + "=" + clirSetting);
          if (clirSetting >= 0) {
              mCi.setCLIR(clirSetting, null);
          }
      }
  
      private void handleRadioAvailable() {
          mCi.getBasebandVersion(obtainMessage(EVENT_GET_BASEBAND_VERSION_DONE));
  
          mCi.getDeviceIdentity(obtainMessage(EVENT_GET_DEVICE_IDENTITY_DONE));
          mCi.getRadioCapability(obtainMessage(EVENT_GET_RADIO_CAPABILITY));
          startLceAfterRadioIsAvailable();
      }
  
      private void handleRadioOn() {
          /* Proactively query voice radio technologies */
          mCi.getVoiceRadioTechnology(obtainMessage(EVENT_REQUEST_VOICE_RADIO_TECH_DONE));
  
          if (!isPhoneTypeGsm()) {
              mCdmaSubscriptionSource = mCdmaSSM.getCdmaSubscriptionSource();
          }
  
          // If this is on APM off, SIM may already be loaded. Send setPreferredNetworkType
          // request to RIL to preserve user setting across APM toggling
          setPreferredNetworkTypeIfSimLoaded();
      }
  
      private void handleRadioOffOrNotAvailable() {
          if (isPhoneTypeGsm()) {
              // Some MMI requests (eg USSD) are not completed
              // within the course of a CommandsInterface request
              // If the radio shuts off or resets while one of these
              // is pending, we need to clean up.
  
              for (int i = mPendingMMIs.size() - 1; i >= 0; i--) {
                  if (((GsmMmiCode) mPendingMMIs.get(i)).isPendingUSSD()) {
                      ((GsmMmiCode) mPendingMMIs.get(i)).onUssdFinishedError();
                  }
              }
          }
          mRadioOffOrNotAvailableRegistrants.notifyRegistrants();
      }
  
      private void handleRadioPowerStateChange() {
          Rlog.d(LOG_TAG, "handleRadioPowerStateChange, state= " + mCi.getRadioState());
          mNotifier.notifyRadioPowerStateChanged(this, mCi.getRadioState());
      }
  
      @Override
      public void handleMessage(Message msg) {
          AsyncResult ar;
          Message onComplete;
  
          switch (msg.what) {
              case EVENT_RADIO_AVAILABLE: {
                  handleRadioAvailable();
              }
              break;
  
              case EVENT_GET_DEVICE_IDENTITY_DONE:{
                  ar = (AsyncResult)msg.obj;
  
                  if (ar.exception != null) {
                      break;
                  }
                  String[] respId = (String[])ar.result;
                  mImei = respId[0];
                  mImeiSv = respId[1];
                  mEsn  =  respId[2];
                  mMeid =  respId[3];
              }
              break;
  
              case EVENT_EMERGENCY_CALLBACK_MODE_ENTER:{
                  handleEnterEmergencyCallbackMode(msg);
              }
              break;
  
              case  EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE:{
                  handleExitEmergencyCallbackMode(msg);
              }
              break;
  
              case EVENT_MODEM_RESET: {
                  logd("Event EVENT_MODEM_RESET Received" + " isInEcm = " + isInEcm()
                          + " isPhoneTypeGsm = " + isPhoneTypeGsm() + " mImsPhone = " + mImsPhone);
                  if (isInEcm()) {
                      if (isPhoneTypeGsm()) {
                          if (mImsPhone != null) {
                              mImsPhone.handleExitEmergencyCallbackMode();
                          }
                      } else {
                          handleExitEmergencyCallbackMode(msg);
                      }
                  }
              }
              break;
  
              case EVENT_RUIM_RECORDS_LOADED:
                  logd("Event EVENT_RUIM_RECORDS_LOADED Received");
                  updateCurrentCarrierInProvider();
                  break;
  
              case EVENT_RADIO_ON:
                  logd("Event EVENT_RADIO_ON Received");
                  handleRadioOn();
                  break;
  
              case EVENT_RIL_CONNECTED:
                  ar = (AsyncResult) msg.obj;
                  if (ar.exception == null && ar.result != null) {
                      mRilVersion = (Integer) ar.result;
                  } else {
                      logd("Unexpected exception on EVENT_RIL_CONNECTED");
                      mRilVersion = -1;
                  }
                  break;
  
              case EVENT_VOICE_RADIO_TECH_CHANGED:
              case EVENT_REQUEST_VOICE_RADIO_TECH_DONE:
                  String what = (msg.what == EVENT_VOICE_RADIO_TECH_CHANGED) ?
                          "EVENT_VOICE_RADIO_TECH_CHANGED" : "EVENT_REQUEST_VOICE_RADIO_TECH_DONE";
                  ar = (AsyncResult) msg.obj;
                  if (ar.exception == null) {
                      if ((ar.result != null) && (((int[]) ar.result).length != 0)) {
                          int newVoiceTech = ((int[]) ar.result)[0];
                          logd(what + ": newVoiceTech=" + newVoiceTech);
                          phoneObjectUpdater(newVoiceTech);
                      } else {
                          loge(what + ": has no tech!");
                      }
                  } else {
                      loge(what + ": exception=" + ar.exception);
                  }
                  break;
  
              case EVENT_UPDATE_PHONE_OBJECT:
                  phoneObjectUpdater(msg.arg1);
                  break;
  
              case EVENT_CARRIER_CONFIG_CHANGED:
                  // Only check for the voice radio tech if it not going to be updated by the voice
                  // registration changes.
                  if (!mContext.getResources().getBoolean(com.android.internal.R.bool.
                          config_switch_phone_on_voice_reg_state_change)) {
                      mCi.getVoiceRadioTechnology(obtainMessage(EVENT_REQUEST_VOICE_RADIO_TECH_DONE));
                  }
                  // Force update IMS service if it is available, if it isn't the config will be
                  // updated when ImsPhoneCallTracker opens a connection.
                  ImsManager imsManager = ImsManager.getInstance(mContext, mPhoneId);
                  if (imsManager.isServiceAvailable()) {
                      imsManager.updateImsServiceConfig(true);
                  } else {
                      logd("ImsManager is not available to update CarrierConfig.");
                  }
  
                  // Update broadcastEmergencyCallStateChanges
                  CarrierConfigManager configMgr = (CarrierConfigManager)
                          getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
                  PersistableBundle b = configMgr.getConfigForSubId(getSubId());
                  if (b != null) {
                      boolean broadcastEmergencyCallStateChanges = b.getBoolean(
                              CarrierConfigManager.KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL);
                      logd("broadcastEmergencyCallStateChanges = " +
                              broadcastEmergencyCallStateChanges);
                      setBroadcastEmergencyCallStateChanges(broadcastEmergencyCallStateChanges);
                  } else {
                      loge("didn't get broadcastEmergencyCallStateChanges from carrier config");
                  }
  
                  // Changing the cdma roaming settings based carrier config.
                  if (b != null) {
                      int config_cdma_roaming_mode = b.getInt(
                              CarrierConfigManager.KEY_CDMA_ROAMING_MODE_INT);
                      int current_cdma_roaming_mode =
                              Settings.Global.getInt(getContext().getContentResolver(),
                              Settings.Global.CDMA_ROAMING_MODE,
                              TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT);
                      switch (config_cdma_roaming_mode) {
                          // Carrier's cdma_roaming_mode will overwrite the user's previous settings
                          // Keep the user's previous setting in global variable which will be used
                          // when carrier's setting is turn off.
                          case TelephonyManager.CDMA_ROAMING_MODE_HOME:
                          case TelephonyManager.CDMA_ROAMING_MODE_AFFILIATED:
                          case TelephonyManager.CDMA_ROAMING_MODE_ANY:
                              logd("cdma_roaming_mode is going to changed to "
                                      + config_cdma_roaming_mode);
                              setCdmaRoamingPreference(config_cdma_roaming_mode,
                                      obtainMessage(EVENT_SET_ROAMING_PREFERENCE_DONE));
                              break;
  
                          // When carrier's setting is turn off, change the cdma_roaming_mode to the
                          // previous user's setting
                          case TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT:
                              if (current_cdma_roaming_mode != config_cdma_roaming_mode) {
                                  logd("cdma_roaming_mode is going to changed to "
                                          + current_cdma_roaming_mode);
                                  setCdmaRoamingPreference(current_cdma_roaming_mode,
                                          obtainMessage(EVENT_SET_ROAMING_PREFERENCE_DONE));
                              }
  
                          default:
                              loge("Invalid cdma_roaming_mode settings: "
                                      + config_cdma_roaming_mode);
                      }
                  } else {
                      loge("didn't get the cdma_roaming_mode changes from the carrier config.");
                  }
                  break;
  
              case EVENT_SET_ROAMING_PREFERENCE_DONE:
                  logd("cdma_roaming_mode change is done");
                  break;
  
              case EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED:
                  logd("EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED");
                  mCdmaSubscriptionSource = mCdmaSSM.getCdmaSubscriptionSource();
                  break;
  
              case EVENT_REGISTERED_TO_NETWORK:
                  logd("Event EVENT_REGISTERED_TO_NETWORK Received");
                  if (isPhoneTypeGsm()) {
                      syncClirSetting();
                  }
                  break;
  
              case EVENT_SIM_RECORDS_LOADED:
                  updateCurrentCarrierInProvider();
  
                  // Check if this is a different SIM than the previous one. If so unset the
                  // voice mail number.
                  String imsi = getVmSimImsi();
                  String imsiFromSIM = getSubscriberId();
                  if ((!isPhoneTypeGsm() || imsi != null) && imsiFromSIM != null
                          && !imsiFromSIM.equals(imsi)) {
                      storeVoiceMailNumber(null);
                      setVmSimImsi(null);
                  }
  
                  updateVoiceMail();
  
                  mSimRecordsLoadedRegistrants.notifyRegistrants();
                  break;
  
              case EVENT_GET_BASEBAND_VERSION_DONE:
                  ar = (AsyncResult)msg.obj;
  
                  if (ar.exception != null) {
                      break;
                  }
  
                  if (DBG) logd("Baseband version: " + ar.result);
                  TelephonyManager.from(mContext).setBasebandVersionForPhone(getPhoneId(),
                          (String)ar.result);
              break;
  
              case EVENT_GET_IMEI_DONE:
                  ar = (AsyncResult)msg.obj;
  
                  if (ar.exception != null) {
                      break;
                  }
  
                  mImei = (String)ar.result;
              break;
  
              case EVENT_GET_IMEISV_DONE:
                  ar = (AsyncResult)msg.obj;
  
                  if (ar.exception != null) {
                      break;
                  }
  
                  mImeiSv = (String)ar.result;
              break;
  
              case EVENT_USSD:
                  ar = (AsyncResult)msg.obj;
  
                  String[] ussdResult = (String[]) ar.result;
  
                  if (ussdResult.length > 1) {
                      try {
                          onIncomingUSSD(Integer.parseInt(ussdResult[0]), ussdResult[1]);
                      } catch (NumberFormatException e) {
                          Rlog.w(LOG_TAG, "error parsing USSD");
                      }
                  }
              break;
  
              case EVENT_RADIO_OFF_OR_NOT_AVAILABLE: {
                  logd("Event EVENT_RADIO_OFF_OR_NOT_AVAILABLE Received");
                  handleRadioOffOrNotAvailable();
                  break;
              }
  
              case EVENT_RADIO_STATE_CHANGED: {
                  logd("EVENT EVENT_RADIO_STATE_CHANGED");
                  handleRadioPowerStateChange();
                  break;
              }
  
              case EVENT_SSN:
                  logd("Event EVENT_SSN Received");
                  if (isPhoneTypeGsm()) {
                      ar = (AsyncResult) msg.obj;
                      SuppServiceNotification not = (SuppServiceNotification) ar.result;
                      mSsnRegistrants.notifyRegistrants(ar);
                  }
                  break;
  
              case EVENT_SET_CALL_FORWARD_DONE:
                  ar = (AsyncResult)msg.obj;
                  IccRecords r = mIccRecords.get();
                  Cfu cfu = (Cfu) ar.userObj;
                  if (ar.exception == null && r != null) {
                      setVoiceCallForwardingFlag(1, msg.arg1 == 1, cfu.mSetCfNumber);
                  }
                  if (cfu.mOnComplete != null) {
                      AsyncResult.forMessage(cfu.mOnComplete, ar.result, ar.exception);
                      cfu.mOnComplete.sendToTarget();
                  }
                  break;
  
              case EVENT_SET_VM_NUMBER_DONE:
                  ar = (AsyncResult)msg.obj;
                  if (((isPhoneTypeGsm() || mSimRecords != null)
                          && IccVmNotSupportedException.class.isInstance(ar.exception))
                          || (!isPhoneTypeGsm() && mSimRecords == null
                          && IccException.class.isInstance(ar.exception))) {
                      storeVoiceMailNumber(mVmNumber);
                      ar.exception = null;
                  }
                  onComplete = (Message) ar.userObj;
                  if (onComplete != null) {
                      AsyncResult.forMessage(onComplete, ar.result, ar.exception);
                      onComplete.sendToTarget();
                  }
                  break;
  
  
              case EVENT_GET_CALL_FORWARD_DONE:
                  ar = (AsyncResult)msg.obj;
                  if (ar.exception == null) {
                      handleCfuQueryResult((CallForwardInfo[])ar.result);
                  }
                  onComplete = (Message) ar.userObj;
                  if (onComplete != null) {
                      AsyncResult.forMessage(onComplete, ar.result, ar.exception);
                      onComplete.sendToTarget();
                  }
                  break;
  
              case EVENT_SET_NETWORK_AUTOMATIC:
                  // Automatic network selection from EF_CSP SIM record
                  ar = (AsyncResult) msg.obj;
                  if (mSST.mSS.getIsManualSelection()) {
                      setNetworkSelectionModeAutomatic((Message) ar.result);
                      logd("SET_NETWORK_SELECTION_AUTOMATIC: set to automatic");
                  } else {
                      // prevent duplicate request which will push current PLMN to low priority
                      logd("SET_NETWORK_SELECTION_AUTOMATIC: already automatic, ignore");
                  }
                  break;
  
              case EVENT_ICC_RECORD_EVENTS:
                  ar = (AsyncResult)msg.obj;
                  processIccRecordEvents((Integer)ar.result);
                  break;
  
              case EVENT_SET_CLIR_COMPLETE:
                  ar = (AsyncResult)msg.obj;
                  if (ar.exception == null) {
                      saveClirSetting(msg.arg1);
                  }
                  onComplete = (Message) ar.userObj;
                  if (onComplete != null) {
                      AsyncResult.forMessage(onComplete, ar.result, ar.exception);
                      onComplete.sendToTarget();
                  }
                  break;
  
              case EVENT_SS:
                  ar = (AsyncResult)msg.obj;
                  logd("Event EVENT_SS received");
                  if (isPhoneTypeGsm()) {
                      // SS data is already being handled through MMI codes.
                      // So, this result if processed as MMI response would help
                      // in re-using the existing functionality.
                      GsmMmiCode mmi = new GsmMmiCode(this, mUiccApplication.get());
                      mmi.processSsData(ar);
                  }
                  break;
  
              case EVENT_GET_RADIO_CAPABILITY:
                  ar = (AsyncResult) msg.obj;
                  RadioCapability rc = (RadioCapability) ar.result;
                  if (ar.exception != null) {
                      Rlog.d(LOG_TAG, "get phone radio capability fail, no need to change " +
                              "mRadioCapability");
                  } else {
                      radioCapabilityUpdated(rc);
                  }
                  Rlog.d(LOG_TAG, "EVENT_GET_RADIO_CAPABILITY: phone rc: " + rc);
                  break;
              case EVENT_VRS_OR_RAT_CHANGED:
                  ar = (AsyncResult) msg.obj;
                  Pair<Integer, Integer> vrsRatPair = (Pair<Integer, Integer>) ar.result;
                  onVoiceRegStateOrRatChanged(vrsRatPair.first, vrsRatPair.second);
                  break;
  
              case EVENT_SET_CARRIER_DATA_ENABLED:
                  ar = (AsyncResult) msg.obj;
                  boolean enabled = (boolean) ar.result;
                  mDataEnabledSettings.setCarrierDataEnabled(enabled);
                  break;
              case EVENT_DEVICE_PROVISIONED_CHANGE:
                  mDataEnabledSettings.updateProvisionedChanged();
                  break;
              case EVENT_DEVICE_PROVISIONING_DATA_SETTING_CHANGE:
                  mDataEnabledSettings.updateProvisioningDataEnabled();
                  break;
              case EVENT_GET_AVAILABLE_NETWORKS_DONE:
                  ar = (AsyncResult) msg.obj;
                  if (ar.exception == null && ar.result != null && mSST != null) {
                      List<OperatorInfo> operatorInfoList = (List<OperatorInfo>) ar.result;
                      List<OperatorInfo> filteredInfoList = new ArrayList<>();
                      for (OperatorInfo operatorInfo : operatorInfoList) {
                          if (OperatorInfo.State.CURRENT == operatorInfo.getState()) {
                              filteredInfoList.add(new OperatorInfo(
                                      mSST.filterOperatorNameByPattern(
                                              operatorInfo.getOperatorAlphaLong()),
                                      mSST.filterOperatorNameByPattern(
                                              operatorInfo.getOperatorAlphaShort()),
                                      operatorInfo.getOperatorNumeric(),
                                      operatorInfo.getState()
                              ));
                          } else {
                              filteredInfoList.add(operatorInfo);
                          }
                      }
                      ar.result = filteredInfoList;
                  }
  
                  onComplete = (Message) ar.userObj;
                  if (onComplete != null) {
                      AsyncResult.forMessage(onComplete, ar.result, ar.exception);
                      onComplete.sendToTarget();
                  }
                  break;
              default:
                  super.handleMessage(msg);
          }
      }
  
      public UiccCardApplication getUiccCardApplication() {
          if (isPhoneTypeGsm()) {
              return mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_3GPP);
          } else {
              return mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_3GPP2);
          }
      }
  
      // todo: check if ICC availability needs to be handled here. mSimRecords should not be needed
      // now because APIs can be called directly on UiccProfile, and that should handle the requests
      // correctly based on supported apps, voice RAT, etc.
      @Override
      protected void onUpdateIccAvailability() {
          if (mUiccController == null ) {
              return;
          }
  
          UiccCardApplication newUiccApplication = null;
  
          // Update mIsimUiccRecords
          if (isPhoneTypeGsm() || isPhoneTypeCdmaLte()) {
              newUiccApplication =
                      mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_IMS);
              IsimUiccRecords newIsimUiccRecords = null;
  
              if (newUiccApplication != null) {
                  newIsimUiccRecords = (IsimUiccRecords) newUiccApplication.getIccRecords();
                  if (DBG) logd("New ISIM application found");
              }
              mIsimUiccRecords = newIsimUiccRecords;
          }
  
          // Update mSimRecords
          if (mSimRecords != null) {
              mSimRecords.unregisterForRecordsLoaded(this);
          }
          if (isPhoneTypeCdmaLte() || isPhoneTypeCdma()) {
              newUiccApplication = mUiccController.getUiccCardApplication(mPhoneId,
                      UiccController.APP_FAM_3GPP);
              SIMRecords newSimRecords = null;
              if (newUiccApplication != null) {
                  newSimRecords = (SIMRecords) newUiccApplication.getIccRecords();
              }
              mSimRecords = newSimRecords;
              if (mSimRecords != null) {
                  mSimRecords.registerForRecordsLoaded(this, EVENT_SIM_RECORDS_LOADED, null);
              }
          } else {
              mSimRecords = null;
          }
  
          // Update mIccRecords, mUiccApplication, mIccPhoneBookIntManager
          newUiccApplication = getUiccCardApplication();
          if (!isPhoneTypeGsm() && newUiccApplication == null) {
              logd("can't find 3GPP2 application; trying APP_FAM_3GPP");
              newUiccApplication = mUiccController.getUiccCardApplication(mPhoneId,
                      UiccController.APP_FAM_3GPP);
          }
  
          UiccCardApplication app = mUiccApplication.get();
          if (app != newUiccApplication) {
              if (app != null) {
                  if (DBG) logd("Removing stale icc objects.");
                  if (mIccRecords.get() != null) {
                      unregisterForIccRecordEvents();
                      mIccPhoneBookIntManager.updateIccRecords(null);
                  }
                  mIccRecords.set(null);
                  mUiccApplication.set(null);
              }
              if (newUiccApplication != null) {
                  if (DBG) {
                      logd("New Uicc application found. type = " + newUiccApplication.getType());
                  }
                  final IccRecords iccRecords = newUiccApplication.getIccRecords();
                  mUiccApplication.set(newUiccApplication);
                  mIccRecords.set(iccRecords);
                  registerForIccRecordEvents();
                  mIccPhoneBookIntManager.updateIccRecords(iccRecords);
                  if (iccRecords != null) {
                      final String simOperatorNumeric = iccRecords.getOperatorNumeric();
                      if (DBG) {
                          logd("New simOperatorNumeric = " + simOperatorNumeric);
                      }
                      if (!TextUtils.isEmpty(simOperatorNumeric)) {
                          TelephonyManager.from(mContext).setSimOperatorNumericForPhone(mPhoneId,
                                  simOperatorNumeric);
                      }
                  }
                  updateDataConnectionTracker();
              }
          }
      }
  
      private void processIccRecordEvents(int eventCode) {
          switch (eventCode) {
              case IccRecords.EVENT_CFI:
                  logi("processIccRecordEvents: EVENT_CFI");
                  notifyCallForwardingIndicator();
                  break;
          }
      }
  
      /**
       * Sets the "current" field in the telephony provider according to the SIM's operator
       *
       * @return true for success; false otherwise.
       */
      @Override
      public boolean updateCurrentCarrierInProvider() {
          long currentDds = SubscriptionManager.getDefaultDataSubscriptionId();
          String operatorNumeric = getOperatorNumeric();
  
          logd("updateCurrentCarrierInProvider: mSubId = " + getSubId()
                  + " currentDds = " + currentDds + " operatorNumeric = " + operatorNumeric);
  
          if (!TextUtils.isEmpty(operatorNumeric) && (getSubId() == currentDds)) {
              try {
                  Uri uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current");
                  ContentValues map = new ContentValues();
                  map.put(Telephony.Carriers.NUMERIC, operatorNumeric);
                  mContext.getContentResolver().insert(uri, map);
                  return true;
              } catch (SQLException e) {
                  Rlog.e(LOG_TAG, "Can't store current operator", e);
              }
          }
          return false;
      }
  
      //CDMA
      /**
       * Sets the "current" field in the telephony provider according to the
       * build-time operator numeric property
       *
       * @return true for success; false otherwise.
       */
      private boolean updateCurrentCarrierInProvider(String operatorNumeric) {
          if (isPhoneTypeCdma()
                  || (isPhoneTypeCdmaLte() && mUiccController.getUiccCardApplication(mPhoneId,
                          UiccController.APP_FAM_3GPP) == null)) {
              logd("CDMAPhone: updateCurrentCarrierInProvider called");
              if (!TextUtils.isEmpty(operatorNumeric)) {
                  try {
                      Uri uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current");
                      ContentValues map = new ContentValues();
                      map.put(Telephony.Carriers.NUMERIC, operatorNumeric);
                      logd("updateCurrentCarrierInProvider from system: numeric=" + operatorNumeric);
                      getContext().getContentResolver().insert(uri, map);
  
                      // Updates MCC MNC device configuration information
                      logd("update mccmnc=" + operatorNumeric);
                      MccTable.updateMccMncConfiguration(mContext, operatorNumeric);
  
                      return true;
                  } catch (SQLException e) {
                      Rlog.e(LOG_TAG, "Can't store current operator", e);
                  }
              }
              return false;
          } else { // isPhoneTypeCdmaLte()
              if (DBG) logd("updateCurrentCarrierInProvider not updated X retVal=" + true);
              return true;
          }
      }
  
      private void handleCfuQueryResult(CallForwardInfo[] infos) {
          IccRecords r = mIccRecords.get();
          if (r != null) {
              if (infos == null || infos.length == 0) {
                  // Assume the default is not active
                  // Set unconditional CFF in SIM to false
                  setVoiceCallForwardingFlag(1, false, null);
              } else {
                  for (int i = 0, s = infos.length; i < s; i++) {
                      if ((infos[i].serviceClass & SERVICE_CLASS_VOICE) != 0) {
                          setVoiceCallForwardingFlag(1, (infos[i].status == 1),
                              infos[i].number);
                          // should only have the one
                          break;
                      }
                  }
              }
          }
      }
  
      /**
       * Retrieves the IccPhoneBookInterfaceManager of the GsmCdmaPhone
       */
      @Override
      public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(){
          return mIccPhoneBookIntManager;
      }
  
      /**
       * Activate or deactivate cell broadcast SMS.
       *
       * @param activate 0 = activate, 1 = deactivate
       * @param response Callback message is empty on completion
       */
      @Override
      public void activateCellBroadcastSms(int activate, Message response) {
          loge("[GsmCdmaPhone] activateCellBroadcastSms() is obsolete; use SmsManager");
          response.sendToTarget();
      }
  
      /**
       * Query the current configuration of cdma cell broadcast SMS.
       *
       * @param response Callback message is empty on completion
       */
      @Override
      public void getCellBroadcastSmsConfig(Message response) {
          loge("[GsmCdmaPhone] getCellBroadcastSmsConfig() is obsolete; use SmsManager");
          response.sendToTarget();
      }
  
      /**
       * Configure cdma cell broadcast SMS.
       *
       * @param response Callback message is empty on completion
       */
      @Override
      public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response) {
          loge("[GsmCdmaPhone] setCellBroadcastSmsConfig() is obsolete; use SmsManager");
          response.sendToTarget();
      }
  
      /**
       * Returns true if OTA Service Provisioning needs to be performed.
       */
      @Override
      public boolean needsOtaServiceProvisioning() {
          if (isPhoneTypeGsm()) {
              return false;
          } else {
              return mSST.getOtasp() != TelephonyManager.OTASP_NOT_NEEDED;
          }
      }
  
      @Override
      public boolean isCspPlmnEnabled() {
          IccRecords r = mIccRecords.get();
          return (r != null) ? r.isCspPlmnEnabled() : false;
      }
  
      /**
       * Whether manual select is now allowed and we should set
       * to auto network select mode.
       */
      public boolean shouldForceAutoNetworkSelect() {
  
          int nwMode = Phone.PREFERRED_NT_MODE;
          int subId = getSubId();
  
          // If it's invalid subId, we shouldn't force to auto network select mode.
          if (!SubscriptionManager.isValidSubscriptionId(subId)) {
              return false;
          }
  
          nwMode = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
                      android.provider.Settings.Global.PREFERRED_NETWORK_MODE + subId, nwMode);
  
          logd("shouldForceAutoNetworkSelect in mode = " + nwMode);
          /*
           *  For multimode targets in global mode manual network
           *  selection is disallowed. So we should force auto select mode.
           */
          if (isManualSelProhibitedInGlobalMode()
                  && ((nwMode == TelephonyManager.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA)
                          || (nwMode == TelephonyManager.NETWORK_MODE_GLOBAL)) ){
              logd("Should force auto network select mode = " + nwMode);
              return true;
          } else {
              logd("Should not force auto network select mode = " + nwMode);
          }
  
          /*
           *  Single mode phone with - GSM network modes/global mode
           *  LTE only for 3GPP
           *  LTE centric + 3GPP Legacy
           *  Note: the actual enabling/disabling manual selection for these
           *  cases will be controlled by csp
           */
          return false;
      }
  
      @UnsupportedAppUsage
      private boolean isManualSelProhibitedInGlobalMode() {
          boolean isProhibited = false;
          final String configString = getContext().getResources().getString(com.android.internal.
                  R.string.prohibit_manual_network_selection_in_gobal_mode);
  
          if (!TextUtils.isEmpty(configString)) {
              String[] configArray = configString.split(";");
  
              if (configArray != null &&
                      ((configArray.length == 1 && configArray[0].equalsIgnoreCase("true")) ||
                          (configArray.length == 2 && !TextUtils.isEmpty(configArray[1]) &&
                              configArray[0].equalsIgnoreCase("true") &&
                              isMatchGid(configArray[1])))) {
                              isProhibited = true;
              }
          }
          logd("isManualNetSelAllowedInGlobal in current carrier is " + isProhibited);
          return isProhibited;
      }
  
      private void registerForIccRecordEvents() {
          IccRecords r = mIccRecords.get();
          if (r == null) {
              return;
          }
          if (isPhoneTypeGsm()) {
              r.registerForNetworkSelectionModeAutomatic(
                      this, EVENT_SET_NETWORK_AUTOMATIC, null);
              r.registerForRecordsEvents(this, EVENT_ICC_RECORD_EVENTS, null);
              r.registerForRecordsLoaded(this, EVENT_SIM_RECORDS_LOADED, null);
          } else {
              r.registerForRecordsLoaded(this, EVENT_RUIM_RECORDS_LOADED, null);
              if (isPhoneTypeCdmaLte()) {
                  // notify simRecordsLoaded registrants for cdmaLte phone
                  r.registerForRecordsLoaded(this, EVENT_SIM_RECORDS_LOADED, null);
              }
          }
      }
  
      private void unregisterForIccRecordEvents() {
          IccRecords r = mIccRecords.get();
          if (r == null) {
              return;
          }
          r.unregisterForNetworkSelectionModeAutomatic(this);
          r.unregisterForRecordsEvents(this);
          r.unregisterForRecordsLoaded(this);
      }
  
      @UnsupportedAppUsage
      @Override
      public void exitEmergencyCallbackMode() {
          if (DBG) {
              Rlog.d(LOG_TAG, "exitEmergencyCallbackMode: mImsPhone=" + mImsPhone
                      + " isPhoneTypeGsm=" + isPhoneTypeGsm());
          }
          if (isPhoneTypeGsm()) {
              if (mImsPhone != null) {
                  mImsPhone.exitEmergencyCallbackMode();
              }
          } else {
              if (mWakeLock.isHeld()) {
                  mWakeLock.release();
              }
              // Send a message which will invoke handleExitEmergencyCallbackMode
              mCi.exitEmergencyCallbackMode(obtainMessage(EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE));
          }
      }
  
      //CDMA
      private void handleEnterEmergencyCallbackMode(Message msg) {
          if (DBG) {
              Rlog.d(LOG_TAG, "handleEnterEmergencyCallbackMode, isInEcm()="
                      + isInEcm());
          }
          // if phone is not in Ecm mode, and it's changed to Ecm mode
          if (!isInEcm()) {
              setIsInEcm(true);
  
              // notify change
              sendEmergencyCallbackModeChange();
  
              // Post this runnable so we will automatically exit
              // if no one invokes exitEmergencyCallbackMode() directly.
              long delayInMillis = SystemProperties.getLong(
                      TelephonyProperties.PROPERTY_ECM_EXIT_TIMER, DEFAULT_ECM_EXIT_TIMER_VALUE);
              postDelayed(mExitEcmRunnable, delayInMillis);
              // We don't want to go to sleep while in Ecm
              mWakeLock.acquire();
          }
      }
  
      //CDMA
      private void handleExitEmergencyCallbackMode(Message msg) {
          AsyncResult ar = (AsyncResult)msg.obj;
          if (DBG) {
              Rlog.d(LOG_TAG, "handleExitEmergencyCallbackMode,ar.exception , isInEcm="
                      + ar.exception + isInEcm());
          }
          // Remove pending exit Ecm runnable, if any
          removeCallbacks(mExitEcmRunnable);
  
          if (mEcmExitRespRegistrant != null) {
              mEcmExitRespRegistrant.notifyRegistrant(ar);
          }
          // if exiting ecm success
          if (ar.exception == null) {
              if (isInEcm()) {
                  setIsInEcm(false);
              }
  
              // release wakeLock
              if (mWakeLock.isHeld()) {
                  mWakeLock.release();
              }
  
              // send an Intent
              sendEmergencyCallbackModeChange();
              // Re-initiate data connection
              mDataEnabledSettings.setInternalDataEnabled(true);
              notifyEmergencyCallRegistrants(false);
          }
      }
  
      //CDMA
      public void notifyEmergencyCallRegistrants(boolean started) {
          mEmergencyCallToggledRegistrants.notifyResult(started ? 1 : 0);
      }
  
      //CDMA
      /**
       * Handle to cancel or restart Ecm timer in emergency call back mode
       * if action is CANCEL_ECM_TIMER, cancel Ecm timer and notify apps the timer is canceled;
       * otherwise, restart Ecm timer and notify apps the timer is restarted.
       */
      public void handleTimerInEmergencyCallbackMode(int action) {
          switch(action) {
              case CANCEL_ECM_TIMER:
                  removeCallbacks(mExitEcmRunnable);
                  mEcmTimerResetRegistrants.notifyResult(Boolean.TRUE);
                  break;
              case RESTART_ECM_TIMER:
                  long delayInMillis = SystemProperties.getLong(
                          TelephonyProperties.PROPERTY_ECM_EXIT_TIMER, DEFAULT_ECM_EXIT_TIMER_VALUE);
                  postDelayed(mExitEcmRunnable, delayInMillis);
                  mEcmTimerResetRegistrants.notifyResult(Boolean.FALSE);
                  break;
              default:
                  Rlog.e(LOG_TAG, "handleTimerInEmergencyCallbackMode, unsupported action " + action);
          }
      }
  
      //CDMA
      private static final String IS683A_FEATURE_CODE = "*228";
      private static final int IS683A_FEATURE_CODE_NUM_DIGITS = 4;
      private static final int IS683A_SYS_SEL_CODE_NUM_DIGITS = 2;
      private static final int IS683A_SYS_SEL_CODE_OFFSET = 4;
  
      private static final int IS683_CONST_800MHZ_A_BAND = 0;
      private static final int IS683_CONST_800MHZ_B_BAND = 1;
      private static final int IS683_CONST_1900MHZ_A_BLOCK = 2;
      private static final int IS683_CONST_1900MHZ_B_BLOCK = 3;
      private static final int IS683_CONST_1900MHZ_C_BLOCK = 4;
      private static final int IS683_CONST_1900MHZ_D_BLOCK = 5;
      private static final int IS683_CONST_1900MHZ_E_BLOCK = 6;
      private static final int IS683_CONST_1900MHZ_F_BLOCK = 7;
      private static final int INVALID_SYSTEM_SELECTION_CODE = -1;
  
      // Define the pattern/format for carrier specified OTASP number schema.
      // It separates by comma and/or whitespace.
      private static Pattern pOtaSpNumSchema = Pattern.compile("[,\\s]+");
  
      //CDMA
      private static boolean isIs683OtaSpDialStr(String dialStr) {
          int sysSelCodeInt;
          boolean isOtaspDialString = false;
          int dialStrLen = dialStr.length();
  
          if (dialStrLen == IS683A_FEATURE_CODE_NUM_DIGITS) {
              if (dialStr.equals(IS683A_FEATURE_CODE)) {
                  isOtaspDialString = true;
              }
          } else {
              sysSelCodeInt = extractSelCodeFromOtaSpNum(dialStr);
              switch (sysSelCodeInt) {
                  case IS683_CONST_800MHZ_A_BAND:
                  case IS683_CONST_800MHZ_B_BAND:
                  case IS683_CONST_1900MHZ_A_BLOCK:
                  case IS683_CONST_1900MHZ_B_BLOCK:
                  case IS683_CONST_1900MHZ_C_BLOCK:
                  case IS683_CONST_1900MHZ_D_BLOCK:
                  case IS683_CONST_1900MHZ_E_BLOCK:
                  case IS683_CONST_1900MHZ_F_BLOCK:
                      isOtaspDialString = true;
                      break;
                  default:
                      break;
              }
          }
          return isOtaspDialString;
      }
  
      //CDMA
      /**
       * This function extracts the system selection code from the dial string.
       */
      private static int extractSelCodeFromOtaSpNum(String dialStr) {
          int dialStrLen = dialStr.length();
          int sysSelCodeInt = INVALID_SYSTEM_SELECTION_CODE;
  
          if ((dialStr.regionMatches(0, IS683A_FEATURE_CODE,
                  0, IS683A_FEATURE_CODE_NUM_DIGITS)) &&
                  (dialStrLen >= (IS683A_FEATURE_CODE_NUM_DIGITS +
                          IS683A_SYS_SEL_CODE_NUM_DIGITS))) {
              // Since we checked the condition above, the system selection code
              // extracted from dialStr will not cause any exception
              sysSelCodeInt = Integer.parseInt (
                      dialStr.substring (IS683A_FEATURE_CODE_NUM_DIGITS,
                              IS683A_FEATURE_CODE_NUM_DIGITS + IS683A_SYS_SEL_CODE_NUM_DIGITS));
          }
          if (DBG) Rlog.d(LOG_TAG, "extractSelCodeFromOtaSpNum " + sysSelCodeInt);
          return sysSelCodeInt;
      }
  
      //CDMA
      /**
       * This function checks if the system selection code extracted from
       * the dial string "sysSelCodeInt' is the system selection code specified
       * in the carrier ota sp number schema "sch".
       */
      private static boolean checkOtaSpNumBasedOnSysSelCode(int sysSelCodeInt, String sch[]) {
          boolean isOtaSpNum = false;
          try {
              // Get how many number of system selection code ranges
              int selRc = Integer.parseInt(sch[1]);
              for (int i = 0; i < selRc; i++) {
                  if (!TextUtils.isEmpty(sch[i+2]) && !TextUtils.isEmpty(sch[i+3])) {
                      int selMin = Integer.parseInt(sch[i+2]);
                      int selMax = Integer.parseInt(sch[i+3]);
                      // Check if the selection code extracted from the dial string falls
                      // within any of the range pairs specified in the schema.
                      if ((sysSelCodeInt >= selMin) && (sysSelCodeInt <= selMax)) {
                          isOtaSpNum = true;
                          break;
                      }
                  }
              }
          } catch (NumberFormatException ex) {
              // If the carrier ota sp number schema is not correct, we still allow dial
              // and only log the error:
              Rlog.e(LOG_TAG, "checkOtaSpNumBasedOnSysSelCode, error", ex);
          }
          return isOtaSpNum;
      }
  
      //CDMA
      /**
       * The following function checks if a dial string is a carrier specified
       * OTASP number or not by checking against the OTASP number schema stored
       * in PROPERTY_OTASP_NUM_SCHEMA.
       *
       * Currently, there are 2 schemas for carriers to specify the OTASP number:
       * 1) Use system selection code:
       *    The schema is:
       *    SELC,the # of code pairs,min1,max1,min2,max2,...
       *    e.g "SELC,3,10,20,30,40,60,70" indicates that there are 3 pairs of
       *    selection codes, and they are {10,20}, {30,40} and {60,70} respectively.
       *
       * 2) Use feature code:
       *    The schema is:
       *    "FC,length of feature code,feature code".
       *     e.g "FC,2,*2" indicates that the length of the feature code is 2,
       *     and the code itself is "*2".
       */
      private boolean isCarrierOtaSpNum(String dialStr) {
          boolean isOtaSpNum = false;
          int sysSelCodeInt = extractSelCodeFromOtaSpNum(dialStr);
          if (sysSelCodeInt == INVALID_SYSTEM_SELECTION_CODE) {
              return isOtaSpNum;
          }
          // mCarrierOtaSpNumSchema is retrieved from PROPERTY_OTASP_NUM_SCHEMA:
          if (!TextUtils.isEmpty(mCarrierOtaSpNumSchema)) {
              Matcher m = pOtaSpNumSchema.matcher(mCarrierOtaSpNumSchema);
              if (DBG) {
                  Rlog.d(LOG_TAG, "isCarrierOtaSpNum,schema" + mCarrierOtaSpNumSchema);
              }
  
              if (m.find()) {
                  String sch[] = pOtaSpNumSchema.split(mCarrierOtaSpNumSchema);
                  // If carrier uses system selection code mechanism
                  if (!TextUtils.isEmpty(sch[0]) && sch[0].equals("SELC")) {
                      if (sysSelCodeInt!=INVALID_SYSTEM_SELECTION_CODE) {
                          isOtaSpNum=checkOtaSpNumBasedOnSysSelCode(sysSelCodeInt,sch);
                      } else {
                          if (DBG) {
                              Rlog.d(LOG_TAG, "isCarrierOtaSpNum,sysSelCodeInt is invalid");
                          }
                      }
                  } else if (!TextUtils.isEmpty(sch[0]) && sch[0].equals("FC")) {
                      int fcLen =  Integer.parseInt(sch[1]);
                      String fc = sch[2];
                      if (dialStr.regionMatches(0,fc,0,fcLen)) {
                          isOtaSpNum = true;
                      } else {
                          if (DBG) Rlog.d(LOG_TAG, "isCarrierOtaSpNum,not otasp number");
                      }
                  } else {
                      if (DBG) {
                          Rlog.d(LOG_TAG, "isCarrierOtaSpNum,ota schema not supported" + sch[0]);
                      }
                  }
              } else {
                  if (DBG) {
                      Rlog.d(LOG_TAG, "isCarrierOtaSpNum,ota schema pattern not right" +
                              mCarrierOtaSpNumSchema);
                  }
              }
          } else {
              if (DBG) Rlog.d(LOG_TAG, "isCarrierOtaSpNum,ota schema pattern empty");
          }
          return isOtaSpNum;
      }
  
      /**
       * isOTASPNumber: checks a given number against the IS-683A OTASP dial string and carrier
       * OTASP dial string.
       *
       * @param dialStr the number to look up.
       * @return true if the number is in IS-683A OTASP dial string or carrier OTASP dial string
       */
      @Override
      public  boolean isOtaSpNumber(String dialStr) {
          if (isPhoneTypeGsm()) {
              return super.isOtaSpNumber(dialStr);
          } else {
              boolean isOtaSpNum = false;
              String dialableStr = PhoneNumberUtils.extractNetworkPortionAlt(dialStr);
              if (dialableStr != null) {
                  isOtaSpNum = isIs683OtaSpDialStr(dialableStr);
                  if (isOtaSpNum == false) {
                      isOtaSpNum = isCarrierOtaSpNum(dialableStr);
                  }
              }
              if (DBG) Rlog.d(LOG_TAG, "isOtaSpNumber " + isOtaSpNum);
              return isOtaSpNum;
          }
      }
  
      @Override
      public int getOtasp() {
          return mSST.getOtasp();
      }
  
      @Override
      public int getCdmaEriIconIndex() {
          if (isPhoneTypeGsm()) {
              return super.getCdmaEriIconIndex();
          } else {
              return getServiceState().getCdmaEriIconIndex();
          }
      }
  
      /**
       * Returns the CDMA ERI icon mode,
       * 0 - ON
       * 1 - FLASHING
       */
      @Override
      public int getCdmaEriIconMode() {
          if (isPhoneTypeGsm()) {
              return super.getCdmaEriIconMode();
          } else {
              return getServiceState().getCdmaEriIconMode();
          }
      }
  
      /**
       * Returns the CDMA ERI text,
       */
      @UnsupportedAppUsage
      @Override
      public String getCdmaEriText() {
          if (isPhoneTypeGsm()) {
              return super.getCdmaEriText();
          } else {
              int roamInd = getServiceState().getCdmaRoamingIndicator();
              int defRoamInd = getServiceState().getCdmaDefaultRoamingIndicator();
              return mSST.getCdmaEriText(roamInd, defRoamInd);
          }
      }
  
      // Return true if either CSIM or RUIM app is present
      @Override
      public boolean isCdmaSubscriptionAppPresent() {
          UiccCardApplication cdmaApplication =
                  mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_3GPP2);
          return cdmaApplication != null && (cdmaApplication.getType() == AppType.APPTYPE_CSIM ||
                  cdmaApplication.getType() == AppType.APPTYPE_RUIM);
      }
  
      private void phoneObjectUpdater(int newVoiceRadioTech) {
          logd("phoneObjectUpdater: newVoiceRadioTech=" + newVoiceRadioTech);
  
          // Check for a voice over lte replacement
          if (ServiceState.isLte(newVoiceRadioTech)
                  || (newVoiceRadioTech == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN)) {
              CarrierConfigManager configMgr = (CarrierConfigManager)
                      getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
              PersistableBundle b = configMgr.getConfigForSubId(getSubId());
              if (b != null) {
                  int volteReplacementRat =
                          b.getInt(CarrierConfigManager.KEY_VOLTE_REPLACEMENT_RAT_INT);
                  logd("phoneObjectUpdater: volteReplacementRat=" + volteReplacementRat);
                  if (volteReplacementRat != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN &&
                             //In cdma case, replace rat only if csim or ruim app present
                             (ServiceState.isGsm(volteReplacementRat) ||
                             isCdmaSubscriptionAppPresent())) {
                      newVoiceRadioTech = volteReplacementRat;
                  }
              } else {
                  loge("phoneObjectUpdater: didn't get volteReplacementRat from carrier config");
              }
          }
  
          if(mRilVersion == 6 && getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE) {
              /*
               * On v6 RIL, when LTE_ON_CDMA is TRUE, always create CDMALTEPhone
               * irrespective of the voice radio tech reported.
               */
              if (getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
                  logd("phoneObjectUpdater: LTE ON CDMA property is set. Use CDMA Phone" +
                          " newVoiceRadioTech=" + newVoiceRadioTech +
                          " mActivePhone=" + getPhoneName());
                  return;
              } else {
                  logd("phoneObjectUpdater: LTE ON CDMA property is set. Switch to CDMALTEPhone" +
                          " newVoiceRadioTech=" + newVoiceRadioTech +
                          " mActivePhone=" + getPhoneName());
                  newVoiceRadioTech = ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT;
              }
          } else {
  
              // If the device is shutting down, then there is no need to switch to the new phone
              // which might send unnecessary attach request to the modem.
              if (isShuttingDown()) {
                  logd("Device is shutting down. No need to switch phone now.");
                  return;
              }
  
              boolean matchCdma = ServiceState.isCdma(newVoiceRadioTech);
              boolean matchGsm = ServiceState.isGsm(newVoiceRadioTech);
              if ((matchCdma && getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) ||
                      (matchGsm && getPhoneType() == PhoneConstants.PHONE_TYPE_GSM)) {
                  // Nothing changed. Keep phone as it is.
                  logd("phoneObjectUpdater: No change ignore," +
                          " newVoiceRadioTech=" + newVoiceRadioTech +
                          " mActivePhone=" + getPhoneName());
                  return;
              }
              if (!matchCdma && !matchGsm) {
                  loge("phoneObjectUpdater: newVoiceRadioTech=" + newVoiceRadioTech +
                          " doesn't match either CDMA or GSM - error! No phone change");
                  return;
              }
          }
  
          if (newVoiceRadioTech == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
              // We need some voice phone object to be active always, so never
              // delete the phone without anything to replace it with!
              logd("phoneObjectUpdater: Unknown rat ignore, "
                      + " newVoiceRadioTech=Unknown. mActivePhone=" + getPhoneName());
              return;
          }
  
          boolean oldPowerState = false; // old power state to off
          if (mResetModemOnRadioTechnologyChange) {
              if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON) {
                  oldPowerState = true;
                  logd("phoneObjectUpdater: Setting Radio Power to Off");
                  mCi.setRadioPower(false, null);
              }
          }
  
          switchVoiceRadioTech(newVoiceRadioTech);
  
          if (mResetModemOnRadioTechnologyChange && oldPowerState) { // restore power state
              logd("phoneObjectUpdater: Resetting Radio");
              mCi.setRadioPower(oldPowerState, null);
          }
  
          // update voice radio tech in UiccProfile
          UiccProfile uiccProfile = getUiccProfile();
          if (uiccProfile != null) {
              uiccProfile.setVoiceRadioTech(newVoiceRadioTech);
          }
  
          // Send an Intent to the PhoneApp that we had a radio technology change
          Intent intent = new Intent(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
          intent.putExtra(PhoneConstants.PHONE_NAME_KEY, getPhoneName());
          SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhoneId);
          ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_ALL);
      }
  
      private void switchVoiceRadioTech(int newVoiceRadioTech) {
  
          String outgoingPhoneName = getPhoneName();
  
          logd("Switching Voice Phone : " + outgoingPhoneName + " >>> "
                  + (ServiceState.isGsm(newVoiceRadioTech) ? "GSM" : "CDMA"));
  
          if (ServiceState.isCdma(newVoiceRadioTech)) {
              UiccCardApplication cdmaApplication =
                      mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_3GPP2);
              if (cdmaApplication != null && cdmaApplication.getType() == AppType.APPTYPE_RUIM) {
                  switchPhoneType(PhoneConstants.PHONE_TYPE_CDMA);
              } else {
                  switchPhoneType(PhoneConstants.PHONE_TYPE_CDMA_LTE);
              }
          } else if (ServiceState.isGsm(newVoiceRadioTech)) {
              switchPhoneType(PhoneConstants.PHONE_TYPE_GSM);
          } else {
              loge("deleteAndCreatePhone: newVoiceRadioTech=" + newVoiceRadioTech +
                      " is not CDMA or GSM (error) - aborting!");
              return;
          }
      }
  
      @Override
      public void setSignalStrengthReportingCriteria(int[] thresholds, int ran) {
          mCi.setSignalStrengthReportingCriteria(REPORTING_HYSTERESIS_MILLIS, REPORTING_HYSTERESIS_DB,
                  thresholds, ran, null);
      }
  
      @Override
      public void setLinkCapacityReportingCriteria(int[] dlThresholds, int[] ulThresholds, int ran) {
          mCi.setLinkCapacityReportingCriteria(REPORTING_HYSTERESIS_MILLIS, REPORTING_HYSTERESIS_KBPS,
                  REPORTING_HYSTERESIS_KBPS, dlThresholds, ulThresholds, ran, null);
      }
  
      @Override
      public IccSmsInterfaceManager getIccSmsInterfaceManager(){
          return mIccSmsInterfaceManager;
      }
  
      @Override
      public void updatePhoneObject(int voiceRadioTech) {
          logd("updatePhoneObject: radioTechnology=" + voiceRadioTech);
          sendMessage(obtainMessage(EVENT_UPDATE_PHONE_OBJECT, voiceRadioTech, 0, null));
      }
  
      @Override
      public void setImsRegistrationState(boolean registered) {
          mSST.setImsRegistrationState(registered);
      }
  
      @Override
      public boolean getIccRecordsLoaded() {
          UiccProfile uiccProfile = getUiccProfile();
          return uiccProfile != null && uiccProfile.getIccRecordsLoaded();
      }
  
      @Override
      public IccCard getIccCard() {
          // This function doesn't return null for backwards compatability purposes.
          // To differentiate between cases where SIM is absent vs. unknown we return a dummy
          // IccCard with the sim state set.
          IccCard card = getUiccProfile();
          if (card != null) {
              return card;
          } else {
              UiccSlot slot = mUiccController.getUiccSlotForPhone(mPhoneId);
              if (slot == null || slot.isStateUnknown()) {
                  return new IccCard(IccCardConstants.State.UNKNOWN);
              } else {
                  return new IccCard(IccCardConstants.State.ABSENT);
              }
          }
      }
  
      private UiccProfile getUiccProfile() {
          return UiccController.getInstance().getUiccProfileForPhone(mPhoneId);
      }
  
      @Override
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          pw.println("GsmCdmaPhone extends:");
          super.dump(fd, pw, args);
          pw.println(" mPrecisePhoneType=" + mPrecisePhoneType);
          pw.println(" mCT=" + mCT);
          pw.println(" mSST=" + mSST);
          pw.println(" mPendingMMIs=" + mPendingMMIs);
          pw.println(" mIccPhoneBookIntManager=" + mIccPhoneBookIntManager);
          if (VDBG) pw.println(" mImei=" + mImei);
          if (VDBG) pw.println(" mImeiSv=" + mImeiSv);
          if (VDBG) pw.println(" mVmNumber=" + mVmNumber);
          pw.println(" mCdmaSSM=" + mCdmaSSM);
          pw.println(" mCdmaSubscriptionSource=" + mCdmaSubscriptionSource);
          pw.println(" mWakeLock=" + mWakeLock);
          pw.println(" isInEcm()=" + isInEcm());
          if (VDBG) pw.println(" mEsn=" + mEsn);
          if (VDBG) pw.println(" mMeid=" + mMeid);
          pw.println(" mCarrierOtaSpNumSchema=" + mCarrierOtaSpNumSchema);
          if (!isPhoneTypeGsm()) {
              pw.println(" getCdmaEriIconIndex()=" + getCdmaEriIconIndex());
              pw.println(" getCdmaEriIconMode()=" + getCdmaEriIconMode());
              pw.println(" getCdmaEriText()=" + getCdmaEriText());
              pw.println(" isMinInfoReady()=" + isMinInfoReady());
          }
          pw.println(" isCspPlmnEnabled()=" + isCspPlmnEnabled());
          pw.flush();
      }
  
      @Override
      public boolean setOperatorBrandOverride(String brand) {
          if (mUiccController == null) {
              return false;
          }
  
          UiccCard card = mUiccController.getUiccCard(getPhoneId());
          if (card == null) {
              return false;
          }
  
          boolean status = card.setOperatorBrandOverride(brand);
  
          // Refresh.
          if (status) {
              TelephonyManager.from(mContext).setSimOperatorNameForPhone(
                      getPhoneId(), mSST.getServiceProviderName());
              // TODO: check if pollState is need when set operator brand override.
              mSST.pollState();
          }
          return status;
      }
  
      /**
       * @return operator numeric.
       */
      private String getOperatorNumeric() {
          String operatorNumeric = null;
          if (isPhoneTypeGsm()) {
              IccRecords r = mIccRecords.get();
              if (r != null) {
                  operatorNumeric = r.getOperatorNumeric();
              }
          } else { //isPhoneTypeCdmaLte()
              IccRecords curIccRecords = null;
              if (mCdmaSubscriptionSource == CDMA_SUBSCRIPTION_NV) {
                  operatorNumeric = SystemProperties.get("ro.cdma.home.operator.numeric");
              } else if (mCdmaSubscriptionSource == CDMA_SUBSCRIPTION_RUIM_SIM) {
                  UiccCardApplication uiccCardApplication = mUiccApplication.get();
                  if (uiccCardApplication != null
                          && uiccCardApplication.getType() == AppType.APPTYPE_RUIM) {
                      logd("Legacy RUIM app present");
                      curIccRecords = mIccRecords.get();
                  } else {
                      // Use sim-records for SimApp, USimApp, CSimApp and ISimApp.
                      curIccRecords = mSimRecords;
                  }
                  if (curIccRecords != null && curIccRecords == mSimRecords) {
                      operatorNumeric = curIccRecords.getOperatorNumeric();
                  } else {
                      curIccRecords = mIccRecords.get();
                      if (curIccRecords != null && (curIccRecords instanceof RuimRecords)) {
                          RuimRecords csim = (RuimRecords) curIccRecords;
                          operatorNumeric = csim.getRUIMOperatorNumeric();
                      }
                  }
              }
              if (operatorNumeric == null) {
                  loge("getOperatorNumeric: Cannot retrieve operatorNumeric:"
                          + " mCdmaSubscriptionSource = " + mCdmaSubscriptionSource +
                          " mIccRecords = " + ((curIccRecords != null) ?
                          curIccRecords.getRecordsLoaded() : null));
              }
  
              logd("getOperatorNumeric: mCdmaSubscriptionSource = " + mCdmaSubscriptionSource
                      + " operatorNumeric = " + operatorNumeric);
  
          }
          return operatorNumeric;
      }
  
      /**
       * @return The country ISO for the subscription associated with this phone.
       */
      public String getCountryIso() {
          int subId = getSubId();
          SubscriptionInfo subInfo = SubscriptionManager.from(getContext())
                  .getActiveSubscriptionInfo(subId);
          if (subInfo == null) {
              return null;
          }
          return subInfo.getCountryIso().toUpperCase();
      }
  
      public void notifyEcbmTimerReset(Boolean flag) {
          mEcmTimerResetRegistrants.notifyResult(flag);
      }
  
      private static final int[] VOICE_PS_CALL_RADIO_TECHNOLOGY = {
              ServiceState.RIL_RADIO_TECHNOLOGY_LTE,
              ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA,
              ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
      };
  
      /**
       * Calculates current RIL voice radio technology for CS calls.
       *
       * This function should only be used in {@link com.android.internal.telephony.GsmCdmaConnection}
       * to indicate current CS call radio technology.
       *
       * @return the RIL voice radio technology used for CS calls,
       *         see {@code RIL_RADIO_TECHNOLOGY_*} in {@link android.telephony.ServiceState}.
       */
      public @ServiceState.RilRadioTechnology int getCsCallRadioTech() {
          int calcVrat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
          if (mSST != null) {
              calcVrat = getCsCallRadioTech(mSST.mSS.getVoiceRegState(),
                      mSST.mSS.getRilVoiceRadioTechnology());
          }
  
          return calcVrat;
      }
  
      /**
       * Calculates current RIL voice radio technology for CS calls based on current voice
       * registration state and technology.
       *
       * Mark current RIL voice radio technology as unknow when any of below condtion is met:
       *  1) Current RIL voice registration state is not in-service.
       *  2) Current RIL voice radio technology is PS call technology, which means CSFB will
       *     happen later after call connection is established.
       *     It is inappropriate to notify upper layer the PS call technology while current call
       *     is CS call, so before CSFB happens, mark voice radio technology as unknow.
       *     After CSFB happens, {@link #onVoiceRegStateOrRatChanged} will update voice call radio
       *     technology with correct value.
       *
       * @param vrs the voice registration state
       * @param vrat the RIL voice radio technology
       *
       * @return the RIL voice radio technology used for CS calls,
       *         see {@code RIL_RADIO_TECHNOLOGY_*} in {@link android.telephony.ServiceState}.
       */
      private @ServiceState.RilRadioTechnology int getCsCallRadioTech(int vrs, int vrat) {
          logd("getCsCallRadioTech, current vrs=" + vrs + ", vrat=" + vrat);
          int calcVrat = vrat;
          if (vrs != ServiceState.STATE_IN_SERVICE
                  || ArrayUtils.contains(VOICE_PS_CALL_RADIO_TECHNOLOGY, vrat)) {
              calcVrat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
          }
  
          logd("getCsCallRadioTech, result calcVrat=" + calcVrat);
          return calcVrat;
      }
  
      /**
       * Handler of RIL Voice Radio Technology changed event.
       */
      private void onVoiceRegStateOrRatChanged(int vrs, int vrat) {
          logd("onVoiceRegStateOrRatChanged");
          mCT.dispatchCsCallRadioTech(getCsCallRadioTech(vrs, vrat));
      }
  
      /**
       * Registration point for Ecm timer reset
       *
       * @param h handler to notify
       * @param what User-defined message code
       * @param obj placed in Message.obj
       */
      @Override
      public void registerForEcmTimerReset(Handler h, int what, Object obj) {
          mEcmTimerResetRegistrants.addUnique(h, what, obj);
      }
  
      @Override
      public void unregisterForEcmTimerReset(Handler h) {
          mEcmTimerResetRegistrants.remove(h);
      }
  
      /**
       * 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 (isPhoneTypeGsm()) {
              IccRecords r = mIccRecords.get();
              if (r != null) {
                  r.setVoiceMessageWaiting(line, countWaiting);
              } else {
                  logd("SIM Records not found, MWI not updated");
              }
          } else {
              setVoiceMessageCount(countWaiting);
          }
      }
  
      @UnsupportedAppUsage
      private void logd(String s) {
          Rlog.d(LOG_TAG, "[" + mPhoneId + "] " + s);
      }
  
      private void logi(String s) {
          Rlog.i(LOG_TAG, "[" + mPhoneId + "] " + s);
      }
  
      @UnsupportedAppUsage
      private void loge(String s) {
          Rlog.e(LOG_TAG, "[" + mPhoneId + "] " + s);
      }
  
      @Override
      public boolean isUtEnabled() {
          Phone imsPhone = mImsPhone;
          if (imsPhone != null) {
              return imsPhone.isUtEnabled();
          } else {
              logd("isUtEnabled: called for GsmCdma");
              return false;
          }
      }
  
      public String getDtmfToneDelayKey() {
          return isPhoneTypeGsm() ?
                  CarrierConfigManager.KEY_GSM_DTMF_TONE_DELAY_INT :
                  CarrierConfigManager.KEY_CDMA_DTMF_TONE_DELAY_INT;
      }
  
      @VisibleForTesting
      public PowerManager.WakeLock getWakeLock() {
          return mWakeLock;
      }
  
      @Override
      public int getLteOnCdmaMode() {
          int currentConfig = super.getLteOnCdmaMode();
          int lteOnCdmaModeDynamicValue = currentConfig;
  
          UiccCardApplication cdmaApplication =
                      mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_3GPP2);
          if (cdmaApplication != null && cdmaApplication.getType() == AppType.APPTYPE_RUIM) {
              //Legacy RUIM cards don't support LTE.
              lteOnCdmaModeDynamicValue = RILConstants.LTE_ON_CDMA_FALSE;
  
              //Override only if static configuration is TRUE.
              if (currentConfig == RILConstants.LTE_ON_CDMA_TRUE) {
                  return lteOnCdmaModeDynamicValue;
              }
          }
          return currentConfig;
      }
  
      private void updateTtyMode(int ttyMode) {
          logi(String.format("updateTtyMode ttyMode=%d", ttyMode));
          setTTYMode(telecomModeToPhoneMode(ttyMode), null);
      }
      private void updateUiTtyMode(int ttyMode) {
          logi(String.format("updateUiTtyMode ttyMode=%d", ttyMode));
          setUiTTYMode(telecomModeToPhoneMode(ttyMode), null);
      }
  
      /**
       * Given a telecom TTY mode, convert to a Telephony mode equivalent.
       * @param telecomMode Telecom TTY mode.
       * @return Telephony phone TTY mode.
       */
      private static int telecomModeToPhoneMode(int telecomMode) {
          switch (telecomMode) {
              // AT command only has 0 and 1, so mapping VCO
              // and HCO to FULL
              case TelecomManager.TTY_MODE_FULL:
              case TelecomManager.TTY_MODE_VCO:
              case TelecomManager.TTY_MODE_HCO:
                  return Phone.TTY_MODE_FULL;
              default:
                  return Phone.TTY_MODE_OFF;
          }
      }
  
      /**
       * Load the current TTY mode in GsmCdmaPhone based on Telecom and UI settings.
       */
      private void loadTtyMode() {
          int ttyMode = TelecomManager.TTY_MODE_OFF;
          TelecomManager telecomManager = TelecomManager.from(mContext);
          if (telecomManager != null) {
              ttyMode = telecomManager.getCurrentTtyMode();
          }
          updateTtyMode(ttyMode);
          //Get preferred TTY mode from settings as UI Tty mode is always user preferred Tty mode.
          ttyMode = Settings.Secure.getInt(mContext.getContentResolver(),
                  Settings.Secure.PREFERRED_TTY_MODE, TelecomManager.TTY_MODE_OFF);
          updateUiTtyMode(ttyMode);
      }
  }
  