/*
 * 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 android.telephony.TelephonyManager.HAL_SERVICE_RADIO;
import static android.telephony.ims.ImsService.CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.BroadcastOptions;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.hardware.radio.modem.ImeiInfo;
import android.net.Uri;
import android.os.AsyncResult;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.Registrant;
import android.os.RegistrantList;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.WorkSource;
import android.preference.PreferenceManager;
import android.sysprop.TelephonyProperties;
import android.telecom.VideoProfile;
import android.telephony.AccessNetworkConstants;
import android.telephony.Annotation.SrvccState;
import android.telephony.CarrierConfigManager;
import android.telephony.CarrierRestrictionRules;
import android.telephony.CellBroadcastIdRange;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.ClientRequestStats;
import android.telephony.DomainSelectionService;
import android.telephony.ImsiEncryptionInfo;
import android.telephony.LinkCapacityEstimate;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneStateListener;
import android.telephony.PhysicalChannelConfig;
import android.telephony.PreciseDataConnectionState;
import android.telephony.RadioAccessFamily;
import android.telephony.RadioAccessSpecifier;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyManager.HalService;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Log;
import android.util.Xml;

import com.android.ims.ImsCall;
import com.android.ims.ImsConfig;
import com.android.ims.ImsException;
import com.android.ims.ImsManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.analytics.TelephonyAnalytics;
import com.android.internal.telephony.data.AccessNetworksManager;
import com.android.internal.telephony.data.DataNetworkController;
import com.android.internal.telephony.data.DataSettingsManager;
import com.android.internal.telephony.data.LinkBandwidthEstimator;
import com.android.internal.telephony.domainselection.DomainSelectionResolver;
import com.android.internal.telephony.emergency.EmergencyConstants;
import com.android.internal.telephony.emergency.EmergencyNumberTracker;
import com.android.internal.telephony.emergency.EmergencyStateTracker;
import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.imsphone.ImsCallInfo;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCall;
import com.android.internal.telephony.metrics.SmsStats;
import com.android.internal.telephony.metrics.VoiceCallSessionStats;
import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.internal.telephony.test.SimulatedRadioControl;
import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
import com.android.internal.telephony.uicc.IccFileHandler;
import com.android.internal.telephony.uicc.IccRecords;
import com.android.internal.telephony.uicc.IsimRecords;
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.UiccPort;
import com.android.internal.telephony.uicc.UsimServiceTable;
import com.android.internal.telephony.util.TelephonyUtils;
import com.android.internal.util.XmlUtils;
import com.android.telephony.Rlog;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * (<em>Not for SDK use</em>)
 * A base implementation for the com.android.internal.telephony.Phone interface.
 *
 * Note that implementations of Phone.java are expected to be used
 * from a single application thread. This should be the same thread that
 * originally called PhoneFactory to obtain the interface.
 *
 *  {@hide}
 *
 */

public abstract class Phone extends Handler implements PhoneInternalInterface {
    private static final String LOG_TAG = "Phone";

    protected final static Object lockForRadioTechnologyChange = new Object();

    protected final int USSD_MAX_QUEUE = 10;

    // Key used to read and write the saved network selection numeric value
    public static final String NETWORK_SELECTION_KEY = "network_selection_key";
    // Key used to read and write the saved network selection operator name
    public static final String NETWORK_SELECTION_NAME_KEY = "network_selection_name_key";
    // Key used to read and write the saved network selection operator short name
    public static final String NETWORK_SELECTION_SHORT_KEY = "network_selection_short_key";


    // Key used to read/write "disable data connection on boot" pref (used for testing)
    public static final String DATA_DISABLED_ON_BOOT_KEY = "disabled_on_boot_key";

    // Key used to read/write data_roaming_is_user_setting pref
    public static final String DATA_ROAMING_IS_USER_SETTING_KEY =
            "data_roaming_is_user_setting_key";

    // Default value when there has been no last emergency SMS time recorded yet.
    private static final int EMERGENCY_SMS_NO_TIME_RECORDED = -1;
    // The max timer value that the platform can be in emergency SMS mode (5 minutes).
    private static final int EMERGENCY_SMS_TIMER_MAX_MS = 300000;

    /* Event Constants */
    protected static final int EVENT_RADIO_AVAILABLE             = 1;
    /** Supplementary Service Notification received. */
    protected static final int EVENT_SSN                         = 2;
    protected static final int EVENT_SIM_RECORDS_LOADED          = 3;
    private static final int EVENT_MMI_DONE                      = 4;
    protected static final int EVENT_RADIO_ON                    = 5;
    protected static final int EVENT_GET_BASEBAND_VERSION_DONE   = 6;
    protected static final int EVENT_USSD                        = 7;
    @VisibleForTesting
    public static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE  = 8;
    private static final int EVENT_GET_SIM_STATUS_DONE           = 11;
    protected static final int EVENT_SET_CALL_FORWARD_DONE       = 12;
    protected static final int EVENT_GET_CALL_FORWARD_DONE       = 13;
    protected static final int EVENT_CALL_RING                   = 14;
    private static final int EVENT_CALL_RING_CONTINUE            = 15;

    // Used to intercept the carrier selection calls so that
    // we can save the values.
    private static final int EVENT_SET_NETWORK_MANUAL_COMPLETE      = 16;
    private static final int EVENT_SET_NETWORK_AUTOMATIC_COMPLETE   = 17;
    protected static final int EVENT_SET_CLIR_COMPLETE              = 18;
    protected static final int EVENT_REGISTERED_TO_NETWORK          = 19;
    protected static final int EVENT_SET_VM_NUMBER_DONE             = 20;
    // Events for CDMA support
    protected static final int EVENT_GET_DEVICE_IDENTITY_DONE       = 21;
    protected static final int EVENT_RUIM_RECORDS_LOADED            = 22;
    protected static final int EVENT_NV_READY                       = 23;
    private static final int EVENT_SET_ENHANCED_VP                  = 24;
    @VisibleForTesting
    public static final int EVENT_EMERGENCY_CALLBACK_MODE_ENTER  = 25;
    protected static final int EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE = 26;
    protected static final int EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED = 27;
    // other
    protected static final int EVENT_SET_NETWORK_AUTOMATIC          = 28;
    protected static final int EVENT_ICC_RECORD_EVENTS              = 29;
    @VisibleForTesting
    protected static final int EVENT_ICC_CHANGED                    = 30;
    // Single Radio Voice Call Continuity
    @VisibleForTesting
    protected static final int EVENT_SRVCC_STATE_CHANGED             = 31;
    private static final int EVENT_INITIATE_SILENT_REDIAL           = 32;
    private static final int EVENT_RADIO_NOT_AVAILABLE              = 33;
    private static final int EVENT_UNSOL_OEM_HOOK_RAW               = 34;
    protected static final int EVENT_GET_RADIO_CAPABILITY           = 35;
    protected static final int EVENT_SS                             = 36;
    private static final int EVENT_CHECK_FOR_NETWORK_AUTOMATIC      = 38;
    protected static final int EVENT_VOICE_RADIO_TECH_CHANGED       = 39;
    protected static final int EVENT_REQUEST_VOICE_RADIO_TECH_DONE  = 40;
    protected static final int EVENT_RIL_CONNECTED                  = 41;
    protected static final int EVENT_UPDATE_PHONE_OBJECT            = 42;
    protected static final int EVENT_CARRIER_CONFIG_CHANGED         = 43;
    // Carrier's CDMA prefer mode setting
    protected static final int EVENT_SET_ROAMING_PREFERENCE_DONE    = 44;
    protected static final int EVENT_MODEM_RESET                    = 45;
    protected static final int EVENT_VRS_OR_RAT_CHANGED             = 46;
    // Radio state change
    protected static final int EVENT_RADIO_STATE_CHANGED            = 47;
    protected static final int EVENT_SET_CARRIER_DATA_ENABLED       = 48;
    protected static final int EVENT_GET_AVAILABLE_NETWORKS_DONE    = 51;

    protected static final int EVENT_UICC_APPS_ENABLEMENT_STATUS_CHANGED  = 53;
    protected static final int EVENT_UICC_APPS_ENABLEMENT_SETTING_CHANGED = 54;
    protected static final int EVENT_GET_UICC_APPS_ENABLEMENT_DONE        = 55;
    protected static final int EVENT_REAPPLY_UICC_APPS_ENABLEMENT_DONE    = 56;
    protected static final int EVENT_REGISTRATION_FAILED = 57;
    protected static final int EVENT_BARRING_INFO_CHANGED = 58;
    protected static final int EVENT_LINK_CAPACITY_CHANGED = 59;
    protected static final int EVENT_RESET_CARRIER_KEY_IMSI_ENCRYPTION = 60;
    protected static final int EVENT_SET_VONR_ENABLED_DONE = 61;
    protected static final int EVENT_SUBSCRIPTIONS_CHANGED = 62;
    protected static final int EVENT_GET_USAGE_SETTING_DONE = 63;
    protected static final int EVENT_SET_USAGE_SETTING_DONE = 64;
    protected static final int EVENT_IMS_DEREGISTRATION_TRIGGERED = 65;
    protected static final int EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE = 66;
    protected static final int EVENT_GET_DEVICE_IMEI_DONE = 67;
    protected static final int EVENT_TRIGGER_NOTIFY_ANBR = 68;
    protected static final int EVENT_GET_N1_MODE_ENABLED_DONE = 69;
    protected static final int EVENT_SET_N1_MODE_ENABLED_DONE = 70;
    protected static final int EVENT_IMEI_MAPPING_CHANGED = 71;
    protected static final int EVENT_CELL_IDENTIFIER_DISCLOSURE = 72;
    protected static final int EVENT_SET_IDENTIFIER_DISCLOSURE_ENABLED_DONE = 73;
    protected static final int EVENT_SECURITY_ALGORITHM_UPDATE = 74;
    protected static final int EVENT_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED_DONE = 75;
    protected static final int EVENT_LAST = EVENT_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED_DONE;

    // For shared prefs.
    private static final String GSM_ROAMING_LIST_OVERRIDE_PREFIX = "gsm_roaming_list_";
    private static final String GSM_NON_ROAMING_LIST_OVERRIDE_PREFIX = "gsm_non_roaming_list_";
    private static final String CDMA_ROAMING_LIST_OVERRIDE_PREFIX = "cdma_roaming_list_";
    private static final String CDMA_NON_ROAMING_LIST_OVERRIDE_PREFIX = "cdma_non_roaming_list_";

    // Key used to read/write current CLIR setting
    public static final String CLIR_KEY = "clir_sub_key";

    // Key used for storing voice mail count
    private static final String VM_COUNT = "vm_count_key";
    // Key used to read/write the ID for storing the voice mail
    private static final String VM_ID = "vm_id_key";

    // Key used for storing call forwarding status
    public static final String CF_STATUS = "cf_status_key";
    // Key used to read/write the ID for storing the call forwarding status
    public static final String CF_ID = "cf_id_key";

    // Key used to read/write "disable DNS server check" pref (used for testing)
    private static final String DNS_SERVER_CHECK_DISABLED_KEY = "dns_server_check_disabled_key";

    // Integer used to let the calling application know that the we are ignoring auto mode switch.
    private static final int ALREADY_IN_AUTO_SELECTION = 1;

    public static final String PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED =
            "pref_null_cipher_and_integrity_enabled";
    private final TelephonyAdminReceiver m2gAdminUpdater;

    public static final String PREF_IDENTIFIER_DISCLOSURE_NOTIFICATIONS_ENABLED =
            "pref_identifier_disclosure_notifications_enabled";

    public static final String PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED =
            "pref_null_cipher_notifications_enabled";

    protected final FeatureFlags mFeatureFlags;

    /**
     * This method is invoked when the Phone exits Emergency Callback Mode.
     */
    protected void handleExitEmergencyCallbackMode() {
    }

    /**
     * Small container class used to hold information relevant to
     * the carrier selection process. operatorNumeric can be ""
     * if we are looking for automatic selection. operatorAlphaLong is the
     * corresponding operator name.
     */
    protected static class NetworkSelectMessage {
        public Message message;
        public String operatorNumeric;
        public String operatorAlphaLong;
        public String operatorAlphaShort;
    }

    public static class SilentRedialParam {
        public String dialString;
        public int causeCode;
        public DialArgs dialArgs;

        public SilentRedialParam(String dialString, int causeCode, DialArgs dialArgs) {
            this.dialString = dialString;
            this.causeCode = causeCode;
            this.dialArgs = dialArgs;
        }
    }

    /* Instance Variables */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public CommandsInterface mCi;
    protected int mVmCount = 0;
    private boolean mDnsCheckDisabled;
    protected DataNetworkController mDataNetworkController;
    /* Used for dispatching signals to configured carrier apps */
    protected CarrierSignalAgent mCarrierSignalAgent;
    /* Used for dispatching carrier action from carrier apps */
    protected CarrierActionAgent mCarrierActionAgent;
    private boolean mDoesRilSendMultipleCallRing;
    private int mCallRingContinueToken;
    private int mCallRingDelay;
    private boolean mIsVoiceCapable = true;
    private final AppSmsManager mAppSmsManager;
    private SimActivationTracker mSimActivationTracker;
    // Keep track of whether or not the phone is in Emergency Callback Mode for Phone and
    // subclasses
    protected boolean mIsPhoneInEcmState = false;
    // Keep track of the case where ECM was cancelled to place another outgoing emergency call.
    // We will need to restart it after the emergency call ends.
    protected boolean mEcmCanceledForEmergency = false;
    private volatile long mTimeLastEmergencySmsSentMs = EMERGENCY_SMS_NO_TIME_RECORDED;

    // Variable to cache the video capability. When RAT changes, we lose this info and are unable
    // to recover from the state. We cache it and notify listeners when they register.
    protected boolean mIsVideoCapable = false;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected UiccController mUiccController = null;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected final AtomicReference<IccRecords> mIccRecords = new AtomicReference<IccRecords>();
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public SmsStorageMonitor mSmsStorageMonitor;
    public SmsUsageMonitor mSmsUsageMonitor;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected AtomicReference<UiccCardApplication> mUiccApplication =
            new AtomicReference<UiccCardApplication>();
    private TelephonyTester mTelephonyTester;
    private String mName;
    private final String mActionDetached;
    private final String mActionAttached;
    protected DeviceStateMonitor mDeviceStateMonitor;
    protected DisplayInfoController mDisplayInfoController;
    protected AccessNetworksManager mAccessNetworksManager;
    // Used for identify the carrier of current subscription
    protected CarrierResolver mCarrierResolver;
    protected SignalStrengthController mSignalStrengthController;

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected int mPhoneId;

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected Phone mImsPhone = null;

    private final AtomicReference<RadioCapability> mRadioCapability =
            new AtomicReference<RadioCapability>();

    protected TelephonyComponentFactory mTelephonyComponentFactory;

    private int mPreferredUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN;
    private int mUsageSettingFromModem = SubscriptionManager.USAGE_SETTING_UNKNOWN;
    private boolean mIsUsageSettingSupported = true;

    //IMS
    /**
     * {@link CallStateException} message text used to indicate that an IMS call has failed because
     * it needs to be retried using GSM or CDMA (e.g. CS fallback).
     * TODO: Replace this with a proper exception; {@link CallStateException} doesn't make sense.
     */
    public static final String CS_FALLBACK = "cs_fallback";

    // Used for retry over cs for supplementary services
    public static final String CS_FALLBACK_SS = "cs_fallback_ss";

    /**
     * @deprecated Use {@link android.telephony.ims.ImsManager#EXTRA_WFC_REGISTRATION_FAILURE_TITLE}
     * instead.
     */
    @Deprecated
    public static final String EXTRA_KEY_ALERT_TITLE =
            android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE;
    /**
     * @deprecated Use
     * {@link android.telephony.ims.ImsManager#EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE} instead.
     */
    @Deprecated
    public static final String EXTRA_KEY_ALERT_MESSAGE =
            android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE;
    public static final String EXTRA_KEY_ALERT_SHOW = "alertShow";
    public static final String EXTRA_KEY_NOTIFICATION_MESSAGE = "notificationMessage";

    protected final RegistrantList mPreciseCallStateRegistrants = new RegistrantList();

    private final RegistrantList mHandoverRegistrants = new RegistrantList();

    private final RegistrantList mNewRingingConnectionRegistrants = new RegistrantList();

    private final RegistrantList mIncomingRingRegistrants = new RegistrantList();

    protected final RegistrantList mDisconnectRegistrants = new RegistrantList();

    private final RegistrantList mServiceStateRegistrants = new RegistrantList();

    protected final RegistrantList mMmiCompleteRegistrants = new RegistrantList();

    @UnsupportedAppUsage
    protected final RegistrantList mMmiRegistrants = new RegistrantList();

    protected final RegistrantList mUnknownConnectionRegistrants = new RegistrantList();

    protected final RegistrantList mSuppServiceFailedRegistrants = new RegistrantList();

    protected final RegistrantList mRadioOffOrNotAvailableRegistrants = new RegistrantList();

    protected final RegistrantList mSimRecordsLoadedRegistrants = new RegistrantList();

    private final RegistrantList mVideoCapabilityChangedRegistrants = new RegistrantList();

    protected final RegistrantList mEmergencyCallToggledRegistrants = new RegistrantList();

    private final RegistrantList mCellInfoRegistrants = new RegistrantList();

    private final RegistrantList mRedialRegistrants = new RegistrantList();

    private final RegistrantList mPhysicalChannelConfigRegistrants = new RegistrantList();

    private final RegistrantList mOtaspRegistrants = new RegistrantList();

    private final RegistrantList mPreferredNetworkTypeRegistrants = new RegistrantList();

    protected Registrant mPostDialHandler;

    protected final LocalLog mLocalLog;

    private Looper mLooper; /* to insure registrants are in correct thread*/

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected final Context mContext;

    protected SubscriptionManagerService mSubscriptionManagerService;

    /**
     * PhoneNotifier is an abstraction for all system-wide
     * state change notification. DefaultPhoneNotifier is
     * used here unless running we're inside a unit test.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected PhoneNotifier mNotifier;

    protected SimulatedRadioControl mSimulatedRadioControl;

    private Map<Integer, Long> mAllowedNetworkTypesForReasons = new HashMap<>();
    private static final String ALLOWED_NETWORK_TYPES_TEXT_USER = "user";
    private static final String ALLOWED_NETWORK_TYPES_TEXT_POWER = "power";
    private static final String ALLOWED_NETWORK_TYPES_TEXT_CARRIER = "carrier";
    private static final String ALLOWED_NETWORK_TYPES_TEXT_ENABLE_2G = "enable_2g";
    private static final int INVALID_ALLOWED_NETWORK_TYPES = -1;
    protected boolean mIsCarrierNrSupported = false;
    protected boolean mIsAllowedNetworkTypesLoadedFromDb = false;
    private boolean mUnitTestMode;

    protected VoiceCallSessionStats mVoiceCallSessionStats;
    protected SmsStats mSmsStats;
    protected TelephonyAnalytics mTelephonyAnalytics;

    protected LinkBandwidthEstimator mLinkBandwidthEstimator;

    public static final int IMEI_TYPE_UNKNOWN = -1;
    public static final int IMEI_TYPE_PRIMARY = ImeiInfo.ImeiType.PRIMARY;
    public static final int IMEI_TYPE_SECONDARY = ImeiInfo.ImeiType.SECONDARY;

    public IccRecords getIccRecords() {
        return mIccRecords.get();
    }

    /**
     * Returns a string identifier for this phone interface for parties
     *  outside the phone app process.
     *  @return The string name.
     */
    @UnsupportedAppUsage
    public String getPhoneName() {
        return mName;
    }

    protected void setPhoneName(String name) {
        mName = name;
    }

    /**
     * Retrieves Nai for phones. Returns null if Nai is not set.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public String getNai(){
         return null;
    }

    /**
     * Return the ActionDetached string. When this action is received by components
     * they are to simulate detaching from the network.
     *
     * @return com.android.internal.telephony.{mName}.action_detached
     *          {mName} is GSM, CDMA ...
     */
    public String getActionDetached() {
        return mActionDetached;
    }

    /**
     * Return the ActionAttached string. When this action is received by components
     * they are to simulate attaching to the network.
     *
     * @return com.android.internal.telephony.{mName}.action_detached
     *          {mName} is GSM, CDMA ...
     */
    public String getActionAttached() {
        return mActionAttached;
    }

    /**
     * Set a system property, unless we're in unit test mode
     */
    // CAF_MSIM TODO this need to be replated with TelephonyManager API ?
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public String getSystemProperty(String property, String defValue) {
        if(getUnitTestMode()) {
            return null;
        }
        return SystemProperties.get(property, defValue);
    }

    /**
     * Constructs a Phone in normal (non-unit test) mode.
     *
     * @param name a name for this phone object
     * @param notifier An instance of DefaultPhoneNotifier,
     * @param context Context object from hosting application
     * unless unit testing.
     * @param ci is CommandsInterface
     * @param unitTestMode when true, prevents notifications
     * of state change events
     * @param featureFlags an instance of the FeatureFlags set
     */
    protected Phone(String name, PhoneNotifier notifier, Context context, CommandsInterface ci,
                    boolean unitTestMode, FeatureFlags featureFlags) {
        this(name, notifier, context, ci, unitTestMode, SubscriptionManager.DEFAULT_PHONE_INDEX,
                TelephonyComponentFactory.getInstance(), featureFlags);
    }

    /**
     * Constructs a Phone in normal (non-unit test) mode.
     *
     * @param name a name for this phone object
     * @param notifier An instance of DefaultPhoneNotifier,
     * @param context Context object from hosting application
     * unless unit testing.
     * @param ci is CommandsInterface
     * @param unitTestMode when true, prevents notifications
     * of state change events
     * @param phoneId the phone-id of this phone.
     * @param telephonyComponentFactory a factory for injecting telephony components
     * @param featureFlags an instance of the FeatureFlags set
     */
    protected Phone(String name, PhoneNotifier notifier, Context context, CommandsInterface ci,
                    boolean unitTestMode, int phoneId,
                    TelephonyComponentFactory telephonyComponentFactory,
                    FeatureFlags featureFlags) {
        mPhoneId = phoneId;
        mName = name;
        mNotifier = notifier;
        mContext = context;
        mLooper = Looper.myLooper();
        mCi = ci;
        mActionDetached = this.getClass().getPackage().getName() + ".action_detached";
        mActionAttached = this.getClass().getPackage().getName() + ".action_attached";
        mAppSmsManager = telephonyComponentFactory.inject(AppSmsManager.class.getName())
                .makeAppSmsManager(context);
        mLocalLog = new LocalLog(64);

        mFeatureFlags = featureFlags;

        setUnitTestMode(unitTestMode);

        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
        mDnsCheckDisabled = sp.getBoolean(DNS_SERVER_CHECK_DISABLED_KEY, false);
        mCi.setOnCallRing(this, EVENT_CALL_RING, null);

        /* "Voice capable" means that this device supports circuit-switched
        * (i.e. voice) phone calls over the telephony network, and is allowed
        * to display the in-call UI while a cellular voice call is active.
        * This will be false on "data only" devices which can't make voice
        * calls and don't support any in-call UI.
        */
        mIsVoiceCapable = ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE))
                .isVoiceCapable();

        /**
         *  Some RIL's don't always send RIL_UNSOL_CALL_RING so it needs
         *  to be generated locally. Ideally all ring tones should be loops
         * and this wouldn't be necessary. But to minimize changes to upper
         * layers it is requested that it be generated by lower layers.
         *
         * By default old phones won't have the property set but do generate
         * the RIL_UNSOL_CALL_RING so the default if there is no property is
         * true.
         */
        mDoesRilSendMultipleCallRing = TelephonyProperties.ril_sends_multiple_call_ring()
                .orElse(true);
        Rlog.d(LOG_TAG, "mDoesRilSendMultipleCallRing=" + mDoesRilSendMultipleCallRing);

        mCallRingDelay = TelephonyProperties.call_ring_delay().orElse(3000);
        Rlog.d(LOG_TAG, "mCallRingDelay=" + mCallRingDelay);

        // Initialize SMS stats
        mSmsStats = new SmsStats(this);

        mSubscriptionManagerService = SubscriptionManagerService.getInstance();
        m2gAdminUpdater = new TelephonyAdminReceiver(context, this);

        if (getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
            return;
        }

        if (TelephonyUtils.IS_DEBUGGABLE) {
            mTelephonyTester = new TelephonyTester(this);
        }

        // Initialize device storage and outgoing SMS usage monitors for SMSDispatchers.
        mTelephonyComponentFactory = telephonyComponentFactory;
        mSmsStorageMonitor = mTelephonyComponentFactory.inject(SmsStorageMonitor.class.getName())
                .makeSmsStorageMonitor(this);
        mSmsUsageMonitor = mTelephonyComponentFactory.inject(SmsUsageMonitor.class.getName())
                .makeSmsUsageMonitor(context);
        mUiccController = UiccController.getInstance();
        mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
        mSimActivationTracker = mTelephonyComponentFactory
                .inject(SimActivationTracker.class.getName())
                .makeSimActivationTracker(this);
        if (getPhoneType() != PhoneConstants.PHONE_TYPE_SIP) {
            mCi.registerForSrvccStateChanged(this, EVENT_SRVCC_STATE_CHANGED, null);
        }
        //Initialize Telephony Analytics
        if (mFeatureFlags.enableTelephonyAnalytics()) {
            mTelephonyAnalytics = new TelephonyAnalytics(this);
        }
    }

    /**
     * Start setup of ImsPhone, which will start trying to connect to the ImsResolver. Will not be
     * called if this device does not support FEATURE_IMS_TELEPHONY.
     */
    public void createImsPhone() {
        if (getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) {
            return;
        }

        synchronized(Phone.lockForRadioTechnologyChange) {
            if (mImsPhone == null) {
                mImsPhone = PhoneFactory.makeImsPhone(mNotifier, this);
                CallManager.getInstance().registerPhone(mImsPhone);
                mImsPhone.registerForSilentRedial(
                        this, EVENT_INITIATE_SILENT_REDIAL, null);
            }
        }
    }

    /**
     * Checks if device should convert CDMA Caller ID restriction related MMI codes to
     * equivalent 3GPP MMI Codes that provide same functionality when device is roaming.
     * This method should only return true on multi-mode devices when carrier requires this
     * conversion to be done on the device.
     *
     * @return true when carrier config
     * "KEY_CONVERT_CDMA_CALLER_ID_MMI_CODES_WHILE_ROAMING_ON_3GPP_BOOL" is set to true
     */
    public boolean supportsConversionOfCdmaCallerIdMmiCodesWhileRoaming() {
        CarrierConfigManager configManager = (CarrierConfigManager)
                getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
        PersistableBundle b = configManager.getConfigForSubId(getSubId());
        if (b != null) {
            return b.getBoolean(
                    CarrierConfigManager
                            .KEY_CONVERT_CDMA_CALLER_ID_MMI_CODES_WHILE_ROAMING_ON_3GPP_BOOL,
                    false);
        } else {
            // Default value set in CarrierConfigManager
            return false;
        }
    }

    /**
     * Check if sending CLIR activation("*31#") and deactivation("#31#") code only without dialing
     * number is prevented.
     *
     * @return {@code true} when carrier config
     * "KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL" is set to {@code true}
     */
    public boolean isClirActivationAndDeactivationPrevented() {
        CarrierConfigManager configManager = (CarrierConfigManager)
                getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
        PersistableBundle b = configManager.getConfigForSubId(getSubId());
        if (b == null) {
            b = CarrierConfigManager.getDefaultConfig();
        }
        return b.getBoolean(
                CarrierConfigManager.KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL);
    }

    /**
     * Determines if the carrier prefers to use an in service sim for a normal routed emergency
     * call.
     * @return true when carrier config
     * {@link CarrierConfigManager#KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL}
     * is true.
     */
    public boolean shouldPreferInServiceSimForNormalRoutedEmergencyCall() {
        CarrierConfigManager configManager = (CarrierConfigManager)
                getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
        PersistableBundle b = configManager.getConfigForSubId(getSubId(), CarrierConfigManager
                .KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL);
        if (b != null) {
            return b.getBoolean(CarrierConfigManager
                            .KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL,
                    false);
        } else {
            // Default value set in CarrierConfigManager
            return false;
        }
    }

    /**
     * When overridden the derived class needs to call
     * super.handleMessage(msg) so this method has a
     * a chance to process the message.
     *
     * @param msg
     */
    @Override
    public void handleMessage(Message msg) {
        AsyncResult ar;

        // messages to be handled whether or not the phone is being destroyed
        // should only include messages which are being re-directed and do not use
        // resources of the phone being destroyed
        switch (msg.what) {
            // handle the select network completion callbacks.
            case EVENT_SET_NETWORK_MANUAL_COMPLETE:
            case EVENT_SET_NETWORK_AUTOMATIC_COMPLETE:
                handleSetSelectNetwork((AsyncResult) msg.obj);
                onSetNetworkSelectionModeCompleted();
                return;
        }

        switch(msg.what) {
            case EVENT_CALL_RING:
                Rlog.d(LOG_TAG, "Event EVENT_CALL_RING Received state=" + getState());
                ar = (AsyncResult)msg.obj;
                if (ar.exception == null) {
                    PhoneConstants.State state = getState();
                    if ((!mDoesRilSendMultipleCallRing)
                            && ((state == PhoneConstants.State.RINGING) ||
                                    (state == PhoneConstants.State.IDLE))) {
                        mCallRingContinueToken += 1;
                        sendIncomingCallRingNotification(mCallRingContinueToken);
                    } else {
                        notifyIncomingRing();
                    }
                }
                break;

            case EVENT_CALL_RING_CONTINUE:
                Rlog.d(LOG_TAG, "Event EVENT_CALL_RING_CONTINUE Received state=" + getState());
                if (getState() == PhoneConstants.State.RINGING) {
                    sendIncomingCallRingNotification(msg.arg1);
                }
                break;

            case EVENT_ICC_CHANGED:
                onUpdateIccAvailability();
                break;

            case EVENT_INITIATE_SILENT_REDIAL:
                // This is an ImsPhone -> GsmCdmaPhone redial
                // See ImsPhone#initiateSilentRedial
                Rlog.d(LOG_TAG, "Event EVENT_INITIATE_SILENT_REDIAL Received");
                ar = (AsyncResult) msg.obj;
                if ((ar.exception == null) && (ar.result != null)) {
                    SilentRedialParam result = (SilentRedialParam) ar.result;
                    String dialString = result.dialString;
                    int causeCode = result.causeCode;
                    DialArgs dialArgs = result.dialArgs;
                    if (TextUtils.isEmpty(dialString)) return;
                    try {
                        Connection cn = dialInternal(dialString, dialArgs);
                        // The ImsPhoneConnection that is owned by the ImsPhone is currently the
                        // one with a callback registered to TelephonyConnection. Notify the
                        // redial happened over that Phone so that it can be replaced with the
                        // new GSM/CDMA Connection.
                        Rlog.d(LOG_TAG, "Notify redial connection changed cn: " + cn);
                        if (mImsPhone != null) {
                            // Don't care it is null or not.
                            mImsPhone.notifyRedialConnectionChanged(cn);
                        }
                    } catch (CallStateException e) {
                        Rlog.e(LOG_TAG, "silent redial failed: " + e);
                        if (mImsPhone != null) {
                            mImsPhone.notifyRedialConnectionChanged(null);
                        }
                    }
                }
                break;

            case EVENT_SRVCC_STATE_CHANGED:
                ar = (AsyncResult)msg.obj;
                if (ar.exception == null) {
                    handleSrvccStateChanged((int[]) ar.result);
                } else {
                    Rlog.e(LOG_TAG, "Srvcc exception: " + ar.exception);
                }
                break;

            case EVENT_UNSOL_OEM_HOOK_RAW:
                // deprecated, ignore
                break;

            case EVENT_CHECK_FOR_NETWORK_AUTOMATIC: {
                onCheckForNetworkSelectionModeAutomatic(msg);
                break;
            }

            case EVENT_GET_USAGE_SETTING_DONE:
                ar = (AsyncResult) msg.obj;
                if (ar.exception == null) {
                    try {
                        mUsageSettingFromModem = ((int[]) ar.result)[0];
                    } catch (NullPointerException | ClassCastException e) {
                        Rlog.e(LOG_TAG, "Invalid response for usage setting " + ar.result);
                        break;
                    }

                    logd("Received mUsageSettingFromModem=" + mUsageSettingFromModem);
                    if (mUsageSettingFromModem != mPreferredUsageSetting) {
                        mCi.setUsageSetting(obtainMessage(EVENT_SET_USAGE_SETTING_DONE),
                                mPreferredUsageSetting);
                    }
                } else {
                    try {
                        CommandException ce = (CommandException) ar.exception;
                        if (ce.getCommandError() == CommandException.Error.REQUEST_NOT_SUPPORTED) {
                            mIsUsageSettingSupported = false;
                        }
                        Rlog.w(LOG_TAG, "Unexpected failure to retrieve usage setting " + ce);
                    } catch (ClassCastException unused) {
                        Rlog.e(LOG_TAG, "Invalid Exception for usage setting " + ar.exception);
                        break; // technically extraneous, but good hygiene
                    }
                }
                break;
            case EVENT_SET_USAGE_SETTING_DONE:
                ar = (AsyncResult) msg.obj;
                if (ar.exception != null) {
                    try {
                        CommandException ce = (CommandException) ar.exception;
                        if (ce.getCommandError() == CommandException.Error.REQUEST_NOT_SUPPORTED) {
                            mIsUsageSettingSupported = false;
                        }
                        Rlog.w(LOG_TAG, "Unexpected failure to set usage setting " + ce);
                    } catch (ClassCastException unused) {
                        Rlog.e(LOG_TAG, "Invalid Exception for usage setting " + ar.exception);
                        break; // technically extraneous, but good hygiene
                    }
                }
                break;
            default:
                throw new RuntimeException("unexpected event not handled, msgId=" + msg.what);
        }
    }

    public ArrayList<Connection> getHandoverConnection() {
        return null;
    }

    /**
     * Notifies the change of the SRVCC state.
     * @param state the new SRVCC state.
     */
    public void notifySrvccState(@SrvccState int state) {
    }

    public void registerForSilentRedial(Handler h, int what, Object obj) {
    }

    public void unregisterForSilentRedial(Handler h) {
    }

    public void registerForVolteSilentRedial(Handler h, int what, Object obj) {
    }

    public void unregisterForVolteSilentRedial(Handler h) {
    }

    private void handleSrvccStateChanged(int[] ret) {
        Rlog.d(LOG_TAG, "handleSrvccStateChanged");

        ArrayList<Connection> conn = null;
        Phone imsPhone = mImsPhone;
        Call.SrvccState srvccState = Call.SrvccState.NONE;
        if (ret != null && ret.length != 0) {
            int state = ret[0];
            if (imsPhone != null) {
                imsPhone.notifySrvccState(state);
            }
            switch(state) {
                case TelephonyManager.SRVCC_STATE_HANDOVER_STARTED:
                    srvccState = Call.SrvccState.STARTED;
                    if (imsPhone != null) {
                        conn = imsPhone.getHandoverConnection();
                        migrateFrom(imsPhone);
                    } else {
                        Rlog.d(LOG_TAG, "HANDOVER_STARTED: mImsPhone null");
                    }
                    break;
                case TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED:
                    srvccState = Call.SrvccState.COMPLETED;
                    break;
                case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED:
                case TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED:
                    srvccState = Call.SrvccState.FAILED;
                    break;

                default:
                    //ignore invalid state
                    return;
            }

            getCallTracker().notifySrvccState(srvccState, conn);

            notifySrvccStateChanged(state);
        }
    }

    /**
     * Gets the context for the phone, as set at initialization time.
     */
    @UnsupportedAppUsage
    public Context getContext() {
        return mContext;
    }

    // Will be called when icc changed
    protected abstract void onUpdateIccAvailability();

    /**
     * Disables the DNS check (i.e., allows "0.0.0.0").
     * Useful for lab testing environment.
     * @param b true disables the check, false enables.
     */
    public void disableDnsCheck(boolean b) {
        mDnsCheckDisabled = b;
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
        SharedPreferences.Editor editor = sp.edit();
        editor.putBoolean(DNS_SERVER_CHECK_DISABLED_KEY, b);
        editor.apply();
    }

    /**
     * Returns true if the DNS check is currently disabled.
     */
    public boolean isDnsCheckDisabled() {
        return mDnsCheckDisabled;
    }

    /**
     * Register for getting notifications for change in the Call State {@link Call.State}
     * This is called PreciseCallState because the call state is more precise than the
     * {@link PhoneConstants.State} which can be obtained using the {@link PhoneStateListener}
     *
     * Resulting events will have an AsyncResult in <code>Message.obj</code>.
     * AsyncResult.userData will be set to the obj argument here.
     * The <em>h</em> parameter is held only by a weak reference.
     */
    @UnsupportedAppUsage
    public void registerForPreciseCallStateChanged(Handler h, int what, Object obj) {
        checkCorrectThread(h);

        mPreciseCallStateRegistrants.addUnique(h, what, obj);
    }

    /**
     * Unregisters for voice call state change notifications.
     * Extraneous calls are tolerated silently.
     */
    @UnsupportedAppUsage
    public void unregisterForPreciseCallStateChanged(Handler h) {
        mPreciseCallStateRegistrants.remove(h);
    }

    /**
     * Notifies when a Handover happens due to SRVCC or Silent Redial
     */
    public void registerForHandoverStateChanged(Handler h, int what, Object obj) {
        checkCorrectThread(h);
        mHandoverRegistrants.addUnique(h, what, obj);
    }

    /**
     * Unregisters for handover state notifications
     */
    public void unregisterForHandoverStateChanged(Handler h) {
        mHandoverRegistrants.remove(h);
    }

    /**
     * Subclasses of Phone probably want to replace this with a
     * version scoped to their packages
     */
    public void notifyHandoverStateChanged(Connection cn) {
       AsyncResult ar = new AsyncResult(null, cn, null);
       mHandoverRegistrants.notifyRegistrants(ar);
    }

    /**
     * Notifies when a Handover happens due to Silent Redial
     */
    public void registerForRedialConnectionChanged(Handler h, int what, Object obj) {
        checkCorrectThread(h);
        mRedialRegistrants.addUnique(h, what, obj);
    }

    /**
     * Unregisters for redial connection notifications
     */
    public void unregisterForRedialConnectionChanged(Handler h) {
        mRedialRegistrants.remove(h);
    }

    /**
     * Subclasses of Phone probably want to replace this with a
     * version scoped to their packages
     */
    public void notifyRedialConnectionChanged(Connection cn) {
        AsyncResult ar = new AsyncResult(null, cn, null);
        mRedialRegistrants.notifyRegistrants(ar);
    }

    protected void setIsInEmergencyCall() {
    }

    /**
     * Notify the phone that an SMS has been sent. This will be used determine if the SMS was sent
     * to an emergency address.
     * @param destinationAddress the address that the SMS was sent to.
     */
    public void notifySmsSent(String destinationAddress) {
        TelephonyManager m = (TelephonyManager) getContext().getSystemService(
                Context.TELEPHONY_SERVICE);
        if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()) {
            if (m != null && m.isEmergencyNumber(destinationAddress)) {
                mLocalLog.log("Emergency SMS detected, recording time.");
                mTimeLastEmergencySmsSentMs = SystemClock.elapsedRealtime();
            }
        } else {
            if (mContext.getPackageManager() != null
                    && mContext.getPackageManager().hasSystemFeature(
                            PackageManager.FEATURE_TELEPHONY_CALLING)) {
                if (m != null && m.isEmergencyNumber(destinationAddress)) {
                    mLocalLog.log("Emergency SMS detected, recording time.");
                    mTimeLastEmergencySmsSentMs = SystemClock.elapsedRealtime();
                }
            }
        }
    }

    /**
     * Determine if the Phone has recently sent an emergency SMS and is still in the interval of
     * time defined by a carrier that we may need to do perform special actions, for example
     * override user setting for location so the carrier can find the user's location for emergency
     * services.
     *
     * @return true if the device is in emergency SMS mode, false otherwise.
     */
    public boolean isInEmergencySmsMode() {
        long lastSmsTimeMs = mTimeLastEmergencySmsSentMs;
        if (lastSmsTimeMs == EMERGENCY_SMS_NO_TIME_RECORDED) {
            // an emergency SMS hasn't been sent since the last check.
            return false;
        }
        CarrierConfigManager configManager = (CarrierConfigManager)
                getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
        PersistableBundle b = configManager.getConfigForSubId(getSubId());
        if (b == null) {
            // default for KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT is 0 and CarrierConfig isn't
            // available, so return false.
            return false;
        }
        int eSmsTimerMs = b.getInt(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT, 0);
        if (eSmsTimerMs == 0) {
            // We do not support this feature for this carrier.
            return false;
        }
        if (eSmsTimerMs > EMERGENCY_SMS_TIMER_MAX_MS) {
            eSmsTimerMs = EMERGENCY_SMS_TIMER_MAX_MS;
        }
        boolean isInEmergencySmsMode = SystemClock.elapsedRealtime()
                <= (lastSmsTimeMs + eSmsTimerMs);
        if (!isInEmergencySmsMode) {
            // Shortcut this next time so we do not have to waste time if another emergency SMS
            // hasn't been sent since the last query.
            mTimeLastEmergencySmsSentMs = EMERGENCY_SMS_NO_TIME_RECORDED;
        } else {
            mLocalLog.log("isInEmergencySmsMode: queried while eSMS mode is active.");
        }
        return isInEmergencySmsMode;
    }

    protected void migrateFrom(Phone from) {
        migrate(mHandoverRegistrants, from.mHandoverRegistrants);
        migrate(mPreciseCallStateRegistrants, from.mPreciseCallStateRegistrants);
        migrate(mNewRingingConnectionRegistrants, from.mNewRingingConnectionRegistrants);
        migrate(mIncomingRingRegistrants, from.mIncomingRingRegistrants);
        migrate(mDisconnectRegistrants, from.mDisconnectRegistrants);
        migrate(mServiceStateRegistrants, from.mServiceStateRegistrants);
        migrate(mMmiCompleteRegistrants, from.mMmiCompleteRegistrants);
        migrate(mMmiRegistrants, from.mMmiRegistrants);
        migrate(mUnknownConnectionRegistrants, from.mUnknownConnectionRegistrants);
        migrate(mSuppServiceFailedRegistrants, from.mSuppServiceFailedRegistrants);
        migrate(mCellInfoRegistrants, from.mCellInfoRegistrants);
        migrate(mRedialRegistrants, from.mRedialRegistrants);
        // The emergency state of IMS phone will be cleared in ImsPhone#notifySrvccState after
        // receive SRVCC completed
        if (from.isInEmergencyCall()) {
            setIsInEmergencyCall();
        }
        setEcmCanceledForEmergency(from.isEcmCanceledForEmergency());
    }

    protected void migrate(RegistrantList to, RegistrantList from) {
        if (from == null) {
            // May be null in some cases, such as testing.
            return;
        }
        from.removeCleared();
        for (int i = 0, n = from.size(); i < n; i++) {
            Registrant r = (Registrant) from.get(i);
            Message msg = r.messageForRegistrant();
            // Since CallManager has already registered with both CS and IMS phones,
            // the migrate should happen only for those registrants which are not
            // registered with CallManager.Hence the below check is needed to add
            // only those registrants to the registrant list which are not
            // coming from the CallManager.
            if (msg != null) {
                if (msg.obj == CallManager.getInstance().getRegistrantIdentifier()) {
                    continue;
                } else {
                    to.add((Registrant) from.get(i));
                }
            } else {
                Rlog.d(LOG_TAG, "msg is null");
            }
        }
    }

    /**
     * Notifies when a previously untracked non-ringing/waiting connection has appeared.
     * This is likely due to some other entity (eg, SIM card application) initiating a call.
     */
    @UnsupportedAppUsage
    public void registerForUnknownConnection(Handler h, int what, Object obj) {
        checkCorrectThread(h);

        mUnknownConnectionRegistrants.addUnique(h, what, obj);
    }

    /**
     * Unregisters for unknown connection notifications.
     */
    @UnsupportedAppUsage
    public void unregisterForUnknownConnection(Handler h) {
        mUnknownConnectionRegistrants.remove(h);
    }

    /**
     * Notifies when a new ringing or waiting connection has appeared.<p>
     *
     *  Messages received from this:
     *  Message.obj will be an AsyncResult
     *  AsyncResult.userObj = obj
     *  AsyncResult.result = a Connection. <p>
     *  Please check Connection.isRinging() to make sure the Connection
     *  has not dropped since this message was posted.
     *  If Connection.isRinging() is true, then
     *   Connection.getCall() == Phone.getRingingCall()
     */
    @UnsupportedAppUsage
    public void registerForNewRingingConnection(
            Handler h, int what, Object obj) {
        checkCorrectThread(h);

        mNewRingingConnectionRegistrants.addUnique(h, what, obj);
    }

    /**
     * Unregisters for new ringing connection notification.
     * Extraneous calls are tolerated silently
     */
    @UnsupportedAppUsage
    public void unregisterForNewRingingConnection(Handler h) {
        mNewRingingConnectionRegistrants.remove(h);
    }

    /**
     * Notifies when phone's video capabilities changes <p>
     *
     *  Messages received from this:
     *  Message.obj will be an AsyncResult
     *  AsyncResult.userObj = obj
     *  AsyncResult.result = true if phone supports video calling <p>
     */
    public void registerForVideoCapabilityChanged(
            Handler h, int what, Object obj) {
        checkCorrectThread(h);

        mVideoCapabilityChangedRegistrants.addUnique(h, what, obj);

        // Notify any registrants of the cached video capability as soon as they register.
        notifyForVideoCapabilityChanged(mIsVideoCapable);
    }

    /**
     * Unregisters for video capability changed notification.
     * Extraneous calls are tolerated silently
     */
    public void unregisterForVideoCapabilityChanged(Handler h) {
        mVideoCapabilityChangedRegistrants.remove(h);
    }

    /**
     * Register for notifications when a sInCall VoicePrivacy is enabled
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj){
        mCi.registerForInCallVoicePrivacyOn(h, what, obj);
    }

    /**
     * Unegister for notifications when a sInCall VoicePrivacy is enabled
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForInCallVoicePrivacyOn(Handler h){
        mCi.unregisterForInCallVoicePrivacyOn(h);
    }

    /**
     * Register for notifications when a sInCall VoicePrivacy is disabled
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj){
        mCi.registerForInCallVoicePrivacyOff(h, what, obj);
    }

    /**
     * Unregister for notifications when a sInCall VoicePrivacy is disabled
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForInCallVoicePrivacyOff(Handler h){
        mCi.unregisterForInCallVoicePrivacyOff(h);
    }

    /**
     * Notifies when an incoming call rings.<p>
     *
     *  Messages received from this:
     *  Message.obj will be an AsyncResult
     *  AsyncResult.userObj = obj
     *  AsyncResult.result = a Connection. <p>
     */
    @UnsupportedAppUsage
    public void registerForIncomingRing(
            Handler h, int what, Object obj) {
        checkCorrectThread(h);

        mIncomingRingRegistrants.addUnique(h, what, obj);
    }

    /**
     * Unregisters for ring notification.
     * Extraneous calls are tolerated silently
     */
    @UnsupportedAppUsage
    public void unregisterForIncomingRing(Handler h) {
        mIncomingRingRegistrants.remove(h);
    }

    /**
     * Notifies when a voice connection has disconnected, either due to local
     * or remote hangup or error.
     *
     *  Messages received from this will have the following members:<p>
     *  <ul><li>Message.obj will be an AsyncResult</li>
     *  <li>AsyncResult.userObj = obj</li>
     *  <li>AsyncResult.result = a Connection object that is
     *  no longer connected.</li></ul>
     */
    @UnsupportedAppUsage
    public void registerForDisconnect(Handler h, int what, Object obj) {
        checkCorrectThread(h);

        mDisconnectRegistrants.addUnique(h, what, obj);
    }

    /**
     * Unregisters for voice disconnection notification.
     * Extraneous calls are tolerated silently
     */
    @UnsupportedAppUsage
    public void unregisterForDisconnect(Handler h) {
        mDisconnectRegistrants.remove(h);
    }

    /**
     * Register for notifications when a supplementary service attempt fails.
     * Message.obj will contain an AsyncResult.
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForSuppServiceFailed(Handler h, int what, Object obj) {
        checkCorrectThread(h);

        mSuppServiceFailedRegistrants.addUnique(h, what, obj);
    }

    /**
     * Unregister for notifications when a supplementary service attempt fails.
     * Extraneous calls are tolerated silently
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForSuppServiceFailed(Handler h) {
        mSuppServiceFailedRegistrants.remove(h);
    }

    /**
     * Register for notifications of initiation of a new MMI code request.
     * MMI codes for GSM are discussed in 3GPP TS 22.030.<p>
     *
     * Example: If Phone.dial is called with "*#31#", then the app will
     * be notified here.<p>
     *
     * The returned <code>Message.obj</code> will contain an AsyncResult.
     *
     * <code>obj.result</code> will be an "MmiCode" object.
     */
    @UnsupportedAppUsage
    public void registerForMmiInitiate(Handler h, int what, Object obj) {
        checkCorrectThread(h);

        mMmiRegistrants.addUnique(h, what, obj);
    }

    /**
     * Unregisters for new MMI initiate notification.
     * Extraneous calls are tolerated silently
     */
    @UnsupportedAppUsage
    public void unregisterForMmiInitiate(Handler h) {
        mMmiRegistrants.remove(h);
    }

    /**
     * Register for notifications that an MMI request has completed
     * its network activity and is in its final state. This may mean a state
     * of COMPLETE, FAILED, or CANCELLED.
     *
     * <code>Message.obj</code> will contain an AsyncResult.
     * <code>obj.result</code> will be an "MmiCode" object
     */
    @UnsupportedAppUsage
    public void registerForMmiComplete(Handler h, int what, Object obj) {
        checkCorrectThread(h);

        mMmiCompleteRegistrants.addUnique(h, what, obj);
    }

    /**
     * Unregisters for MMI complete notification.
     * Extraneous calls are tolerated silently
     */
    @UnsupportedAppUsage
    public void unregisterForMmiComplete(Handler h) {
        checkCorrectThread(h);

        mMmiCompleteRegistrants.remove(h);
    }

    /**
     * Registration point for Sim records loaded
     * @param h handler to notify
     * @param what what code of message when delivered
     * @param obj placed in Message.obj
     */
    @UnsupportedAppUsage
    public void registerForSimRecordsLoaded(Handler h, int what, Object obj) {
    }

    /**
     * Unregister for notifications for Sim records loaded
     * @param h Handler to be removed from the registrant list.
     */
    @UnsupportedAppUsage
    public void unregisterForSimRecordsLoaded(Handler h) {
    }

    /**
     * Register for TTY mode change notifications from the network.
     * Message.obj will contain an AsyncResult.
     * AsyncResult.result will be an Integer containing new mode.
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForTtyModeReceived(Handler h, int what, Object obj) {
    }

    /**
     * Unregisters for TTY mode change notifications.
     * Extraneous calls are tolerated silently
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForTtyModeReceived(Handler h) {
    }

    /**
     * Switches network selection mode to "automatic", re-scanning and
     * re-selecting a network if appropriate.
     *
     * @param response The message to dispatch when the network selection
     * is complete.
     *
     * @see #selectNetworkManually(OperatorInfo, boolean, android.os.Message)
     */
    @UnsupportedAppUsage
    public void setNetworkSelectionModeAutomatic(Message response) {
        Rlog.d(LOG_TAG, "setNetworkSelectionModeAutomatic, querying current mode");
        // we don't want to do this unnecessarily - it actually causes
        // the radio to repeat network selection and is costly
        // first check if we're already in automatic mode
        Message msg = obtainMessage(EVENT_CHECK_FOR_NETWORK_AUTOMATIC);
        msg.obj = response;
        mCi.getNetworkSelectionMode(msg);
    }

    private void onCheckForNetworkSelectionModeAutomatic(Message fromRil) {
        AsyncResult ar = (AsyncResult)fromRil.obj;
        Message response = (Message)ar.userObj;
        boolean doAutomatic = true;
        if (ar.exception == null && ar.result != null) {
            try {
                int[] modes = (int[])ar.result;
                if (modes[0] == 0) {
                    // already confirmed to be in automatic mode - don't resend
                    doAutomatic = false;
                }
            } catch (Exception e) {
                // send the setting on error
            }
        }

        // wrap the response message in our own message along with
        // an empty string (to indicate automatic selection) for the
        // operator's id.
        NetworkSelectMessage nsm = new NetworkSelectMessage();
        nsm.message = response;
        nsm.operatorNumeric = "";
        nsm.operatorAlphaLong = "";
        nsm.operatorAlphaShort = "";

        if (doAutomatic) {
            Rlog.d(LOG_TAG, "setNetworkSelectionModeAutomatic - set network selection auto");
            Message msg = obtainMessage(EVENT_SET_NETWORK_AUTOMATIC_COMPLETE, nsm);
            mCi.setNetworkSelectionModeAutomatic(msg);
        } else {
            Rlog.d(LOG_TAG, "setNetworkSelectionModeAutomatic - already auto, ignoring");
            // let the calling application know that the we are ignoring automatic mode switch.
            if (nsm.message != null) {
                nsm.message.arg1 = ALREADY_IN_AUTO_SELECTION;
            }

            ar.userObj = nsm;
            handleSetSelectNetwork(ar);
        }

        updateSavedNetworkOperator(nsm);
    }

    /**
     * Called when setting network selection mode is complete.
     */
    protected void onSetNetworkSelectionModeCompleted() {
    }

    /**
     * Query the radio for the current network selection mode.
     *
     * Return values:
     *     0 - automatic.
     *     1 - manual.
     */
    public void getNetworkSelectionMode(Message message) {
        mCi.getNetworkSelectionMode(message);
    }

    public List<ClientRequestStats> getClientRequestStats() {
        return mCi.getClientRequestStats();
    }

    /**
     * Manually selects a network. <code>response</code> is
     * dispatched when this is complete.  <code>response.obj</code> will be
     * an AsyncResult, and <code>response.obj.exception</code> will be non-null
     * on failure.
     *
     * @see #setNetworkSelectionModeAutomatic(Message)
     */
    @UnsupportedAppUsage
    public void selectNetworkManually(OperatorInfo network, boolean persistSelection,
            Message response) {
        // wrap the response message in our own message along with
        // the operator's id.
        NetworkSelectMessage nsm = new NetworkSelectMessage();
        nsm.message = response;
        nsm.operatorNumeric = network.getOperatorNumeric();
        nsm.operatorAlphaLong = network.getOperatorAlphaLong();
        nsm.operatorAlphaShort = network.getOperatorAlphaShort();

        Message msg = obtainMessage(EVENT_SET_NETWORK_MANUAL_COMPLETE, nsm);
        mCi.setNetworkSelectionModeManual(network.getOperatorNumeric(), network.getRan(), msg);

        if (persistSelection) {
            updateSavedNetworkOperator(nsm);
        } else {
            clearSavedNetworkSelection();
            updateManualNetworkSelection(nsm);
        }
    }

    /**
     * Registration point for emergency call/callback mode start. Message.obj is AsyncResult and
     * Message.obj.result will be Integer indicating start of call by value 1 or end of call by
     * value 0
     * @param h handler to notify
     * @param what what code of message when delivered
     * @param obj placed in Message.obj.userObj
     */
    public void registerForEmergencyCallToggle(Handler h, int what, Object obj) {
        Registrant r = new Registrant(h, what, obj);
        mEmergencyCallToggledRegistrants.add(r);
    }

    public void unregisterForEmergencyCallToggle(Handler h) {
        mEmergencyCallToggledRegistrants.remove(h);
    }

    private void updateSavedNetworkOperator(NetworkSelectMessage nsm) {
        int subId = getSubId();
        if (SubscriptionManager.isValidSubscriptionId(subId)) {
            // open the shared preferences editor, and write the value.
            // nsm.operatorNumeric is "" if we're in automatic.selection.
            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
            SharedPreferences.Editor editor = sp.edit();
            editor.putString(NETWORK_SELECTION_KEY + subId, nsm.operatorNumeric);
            editor.putString(NETWORK_SELECTION_NAME_KEY + subId, nsm.operatorAlphaLong);
            editor.putString(NETWORK_SELECTION_SHORT_KEY + subId, nsm.operatorAlphaShort);

            // commit and log the result.
            if (!editor.commit()) {
                Rlog.e(LOG_TAG, "failed to commit network selection preference");
            }
        } else {
            Rlog.e(LOG_TAG, "Cannot update network selection preference due to invalid subId " +
                    subId);
        }
    }

    /**
     * Update non-perisited manual network selection.
     *
     * @param nsm PLMN info of the selected network
     */
    protected void updateManualNetworkSelection(NetworkSelectMessage nsm)  {
        Rlog.e(LOG_TAG, "updateManualNetworkSelection() should be overridden");
    }

    /**
     * Used to track the settings upon completion of the network change.
     */
    private void handleSetSelectNetwork(AsyncResult ar) {
        // look for our wrapper within the asyncresult, skip the rest if it
        // is null.
        if (!(ar.userObj instanceof NetworkSelectMessage)) {
            Rlog.e(LOG_TAG, "unexpected result from user object.");
            return;
        }

        NetworkSelectMessage nsm = (NetworkSelectMessage) ar.userObj;

        // found the object, now we send off the message we had originally
        // attached to the request.
        if (nsm.message != null) {
            AsyncResult.forMessage(nsm.message, ar.result, ar.exception);
            nsm.message.sendToTarget();
        }
    }

    /**
     * Method to retrieve the saved operator from the Shared Preferences
     */
    @NonNull
    public OperatorInfo getSavedNetworkSelection() {
        // open the shared preferences and search with our key.
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
        String numeric = sp.getString(NETWORK_SELECTION_KEY + getSubId(), "");
        String name = sp.getString(NETWORK_SELECTION_NAME_KEY + getSubId(), "");
        String shrt = sp.getString(NETWORK_SELECTION_SHORT_KEY + getSubId(), "");
        return new OperatorInfo(name, shrt, numeric);
    }

    /**
     * Clears the saved network selection.
     */
    private void clearSavedNetworkSelection() {
        // open the shared preferences and search with our key.
        PreferenceManager.getDefaultSharedPreferences(getContext()).edit().
                remove(NETWORK_SELECTION_KEY + getSubId()).
                remove(NETWORK_SELECTION_NAME_KEY + getSubId()).
                remove(NETWORK_SELECTION_SHORT_KEY + getSubId()).commit();
    }

    /**
     * Method to restore the previously saved operator id, or reset to
     * automatic selection, all depending upon the value in the shared
     * preferences.
     */
    private void restoreSavedNetworkSelection(Message response) {
        // retrieve the operator
        OperatorInfo networkSelection = getSavedNetworkSelection();

        // set to auto if the id is empty, otherwise select the network.
        if (networkSelection == null || TextUtils.isEmpty(networkSelection.getOperatorNumeric())) {
            setNetworkSelectionModeAutomatic(response);
        } else {
            selectNetworkManually(networkSelection, true, response);
        }
    }

    /**
     * Saves CLIR setting so that we can re-apply it as necessary
     * (in case the RIL resets it across reboots).
     */
    public void saveClirSetting(int commandInterfaceCLIRMode) {
        // Open the shared preferences editor, and write the value.
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
        SharedPreferences.Editor editor = sp.edit();
        editor.putInt(CLIR_KEY + getSubId(), commandInterfaceCLIRMode);
        Rlog.i(LOG_TAG, "saveClirSetting: " + CLIR_KEY + getSubId() + "="
                + commandInterfaceCLIRMode);

        // Commit and log the result.
        if (!editor.commit()) {
            Rlog.e(LOG_TAG, "Failed to commit CLIR preference");
        }
    }

    /**
     * For unit tests; don't send notifications to "Phone"
     * mailbox registrants if true.
     */
    private void setUnitTestMode(boolean f) {
        mUnitTestMode = f;
    }

    /**
     * @return true If unit test mode is enabled
     */
    public boolean getUnitTestMode() {
        return mUnitTestMode;
    }

    /**
     * To be invoked when a voice call Connection disconnects.
     *
     * Subclasses of Phone probably want to replace this with a
     * version scoped to their packages
     */
    protected void notifyDisconnectP(Connection cn) {
        AsyncResult ar = new AsyncResult(null, cn, null);
        mDisconnectRegistrants.notifyRegistrants(ar);
    }

    /**
     * Register for ServiceState changed.
     * Message.obj will contain an AsyncResult.
     * AsyncResult.result will be a ServiceState instance
     */
    @UnsupportedAppUsage
    public void registerForServiceStateChanged(
            Handler h, int what, Object obj) {
        mServiceStateRegistrants.add(h, what, obj);
    }

    /**
     * Unregisters for ServiceStateChange notification.
     * Extraneous calls are tolerated silently
     */
    @UnsupportedAppUsage
    public void unregisterForServiceStateChanged(Handler h) {
        mServiceStateRegistrants.remove(h);
    }

    /**
     * Notifies when out-band ringback tone is needed.<p>
     *
     *  Messages received from this:
     *  Message.obj will be an AsyncResult
     *  AsyncResult.userObj = obj
     *  AsyncResult.result = boolean, true to start play ringback tone
     *                       and false to stop. <p>
     */
    @UnsupportedAppUsage
    public void registerForRingbackTone(Handler h, int what, Object obj) {
        mCi.registerForRingbackTone(h, what, obj);
    }

    /**
     * Unregisters for ringback tone notification.
     */
    @UnsupportedAppUsage
    public void unregisterForRingbackTone(Handler h) {
        mCi.unregisterForRingbackTone(h);
    }

    /**
     * Notifies when out-band on-hold tone is needed.<p>
     *
     *  Messages received from this:
     *  Message.obj will be an AsyncResult
     *  AsyncResult.userObj = obj
     *  AsyncResult.result = boolean, true to start play on-hold tone
     *                       and false to stop. <p>
     */
    public void registerForOnHoldTone(Handler h, int what, Object obj) {
    }

    /**
     * Unregisters for on-hold tone notification.
     */
    public void unregisterForOnHoldTone(Handler h) {
    }

    /**
     * Registers the handler to reset the uplink mute state to get
     * uplink audio.
     */
    public void registerForResendIncallMute(Handler h, int what, Object obj) {
        mCi.registerForResendIncallMute(h, what, obj);
    }

    /**
     * Unregisters for resend incall mute notifications.
     */
    public void unregisterForResendIncallMute(Handler h) {
        mCi.unregisterForResendIncallMute(h);
    }

    /**
     * Registers for CellInfo changed.
     * Message.obj will contain an AsyncResult.
     * AsyncResult.result will be a List<CellInfo> instance
     */
    public void registerForCellInfo(
            Handler h, int what, Object obj) {
        mCellInfoRegistrants.add(h, what, obj);
    }

    /**
     * Unregisters for CellInfo notification.
     * Extraneous calls are tolerated silently
     */
    public void unregisterForCellInfo(Handler h) {
        mCellInfoRegistrants.remove(h);
    }

    /**
     * Enables or disables echo suppression.
     */
    public void setEchoSuppressionEnabled() {
        // no need for regular phone
    }

    /**
     * Subclasses of Phone probably want to replace this with a
     * version scoped to their packages
     */
    protected void notifyServiceStateChangedP(ServiceState ss) {
        AsyncResult ar = new AsyncResult(null, new ServiceState(ss), null);
        mServiceStateRegistrants.notifyRegistrants(ar);

        mNotifier.notifyServiceState(this);
    }

    /**
     * Version of notifyServiceStateChangedP which allows us to specify the subId. This is used when
     * we send out a final ServiceState update when a phone's subId becomes invalid.
     */
    protected void notifyServiceStateChangedPForSubId(ServiceState ss, int subId) {
        AsyncResult ar = new AsyncResult(null, ss, null);
        mServiceStateRegistrants.notifyRegistrants(ar);

        mNotifier.notifyServiceStateForSubId(this, ss, subId);
    }

    /**
     * If this is a simulated phone interface, returns a SimulatedRadioControl.
     * @return SimulatedRadioControl if this is a simulated interface;
     * otherwise, null.
     */
    public SimulatedRadioControl getSimulatedRadioControl() {
        return mSimulatedRadioControl;
    }

    /**
     * Verifies the current thread is the same as the thread originally
     * used in the initialization of this instance. Throws RuntimeException
     * if not.
     *
     * @exception RuntimeException if the current thread is not
     * the thread that originally obtained this Phone instance.
     */
    private void checkCorrectThread(Handler h) {
        if (h.getLooper() != mLooper) {
            throw new RuntimeException(
                    "com.android.internal.telephony.Phone must be used from within one thread");
        }
    }

    /**
     * Set the properties by matching the carrier string in
     * a string-array resource
     */
    @Nullable Locale getLocaleFromCarrierProperties() {
        String carrier = SystemProperties.get("ro.carrier");

        if (null == carrier || 0 == carrier.length() || "unknown".equals(carrier)) {
            return null;
        }

        CharSequence[] carrierLocales = mContext.getResources().getTextArray(
                R.array.carrier_properties);

        for (int i = 0; i < carrierLocales.length; i+=3) {
            String c = carrierLocales[i].toString();
            if (carrier.equals(c)) {
                return Locale.forLanguageTag(carrierLocales[i + 1].toString().replace('_', '-'));
            }
        }

        return null;
    }

    /**
     * Get current coarse-grained voice call state.
     * Use {@link #registerForPreciseCallStateChanged(Handler, int, Object)
     * registerForPreciseCallStateChanged()} for change notification. <p>
     * If the phone has an active call and call waiting occurs,
     * then the phone state is RINGING not OFFHOOK
     * <strong>Note:</strong>
     * This registration point provides notification of finer-grained
     * changes.<p>
     */
    @UnsupportedAppUsage
    public abstract PhoneConstants.State getState();

    /**
     * Retrieves the IccFileHandler of the Phone instance
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public IccFileHandler getIccFileHandler(){
        UiccCardApplication uiccApplication = mUiccApplication.get();
        IccFileHandler fh;

        if (uiccApplication == null) {
            Rlog.d(LOG_TAG, "getIccFileHandler: uiccApplication == null, return null");
            fh = null;
        } else {
            fh = uiccApplication.getIccFileHandler();
        }

        Rlog.d(LOG_TAG, "getIccFileHandler: fh=" + fh);
        return fh;
    }

    /*
     * Retrieves the Handler of the Phone instance
     */
    public Handler getHandler() {
        return this;
    }

    /**
     * Update the phone object if the voice radio technology has changed
     *
     * @param voiceRadioTech The new voice radio technology
     */
    public void updatePhoneObject(int voiceRadioTech) {
    }

    /**
    * Retrieves the ServiceStateTracker of the phone instance.
    */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public ServiceStateTracker getServiceStateTracker() {
        return null;
    }

    /**
     * Override to merge into {@link #getServiceState} when telecom has registered a SIM call
     * manager that supports over-the-top SIM-based calling (e.g. carrier-provided wi-fi calling
     * implementation).
     *
     * @param hasService Whether or not the SIM call manager currently provides over-the-top voice
     */
    public void setVoiceServiceStateOverride(boolean hasService) {}

    /**
     * Check whether the radio is off for thermal reason.
     *
     * @return {@code true} only if thermal mitigation is one of the reason for which radio is off.
     */
    public boolean isRadioOffForThermalMitigation() {
        ServiceStateTracker sst = getServiceStateTracker();
        return sst != null && sst.getRadioPowerOffReasons()
                .contains(TelephonyManager.RADIO_POWER_REASON_THERMAL);
    }

    /**
     * Retrieves the EmergencyNumberTracker of the phone instance.
     */
    public EmergencyNumberTracker getEmergencyNumberTracker() {
        return null;
    }

    /**
    * Get call tracker
    */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public CallTracker getCallTracker() {
        return null;
    }

    /**
     * @return The instance of access networks manager.
     */
    public AccessNetworksManager getAccessNetworksManager() {
        return null;
    }

    /**
     * Retrieves the DeviceStateMonitor of the phone instance.
     */
    public DeviceStateMonitor getDeviceStateMonitor() {
        return null;
    }

    /**
     * Retrieves the DisplayInfoController of the phone instance.
     */
    public DisplayInfoController getDisplayInfoController() {
        return null;
    }

    /**
     * Retrieves the SignalStrengthController of the phone instance.
     */
    public SignalStrengthController getSignalStrengthController() {
        Log.wtf(LOG_TAG, "getSignalStrengthController return null.");
        return null;
    }

    /**
     * Update voice activation state
     */
    public void setVoiceActivationState(int state) {
        mSimActivationTracker.setVoiceActivationState(state);
    }
    /**
     * Update data activation state
     */
    public void setDataActivationState(int state) {
        mSimActivationTracker.setDataActivationState(state);
    }

    /**
     * Returns voice activation state
     */
    public int getVoiceActivationState() {
        return mSimActivationTracker.getVoiceActivationState();
    }
    /**
     * Returns data activation state
     */
    public int getDataActivationState() {
        return mSimActivationTracker.getDataActivationState();
    }

    /**
     * Update voice mail count related fields and notify listeners
     */
    public void updateVoiceMail() {
        Rlog.e(LOG_TAG, "updateVoiceMail() should be overridden");
    }

    public AppType getCurrentUiccAppType() {
        UiccCardApplication currentApp = mUiccApplication.get();
        if (currentApp != null) {
            return currentApp.getType();
        }
        return AppType.APPTYPE_UNKNOWN;
    }

    /**
     * Returns the ICC card interface for this phone, or null
     * if not applicable to underlying technology.
     */
    @UnsupportedAppUsage
    public IccCard getIccCard() {
        return null;
        //throw new Exception("getIccCard Shouldn't be called from Phone");
    }

    /**
     * Retrieves the serial number of the ICC, if applicable. Returns only the decimal digits before
     * the first hex digit in the ICC ID.
     */
    @UnsupportedAppUsage
    public String getIccSerialNumber() {
        IccRecords r = mIccRecords.get();
        return (r != null) ? r.getIccId() : null;
    }

    /**
     * Retrieves the full serial number of the ICC (including hex digits), if applicable.
     */
    public String getFullIccSerialNumber() {
        IccRecords r = mIccRecords.get();
        return (r != null) ? r.getFullIccId() : null;
    }

    /**
     * Returns SIM record load state. Use
     * <code>getSimCard().registerForReady()</code> for change notification.
     *
     * @return true if records from the SIM have been loaded and are
     * available (if applicable). If not applicable to the underlying
     * technology, returns true as well.
     */
    public boolean getIccRecordsLoaded() {
        IccRecords r = mIccRecords.get();
        return (r != null) ? r.getRecordsLoaded() : false;
    }

    /** Set the minimum interval for CellInfo requests to the modem */
    public void setCellInfoMinInterval(int interval) {
        getServiceStateTracker().setCellInfoMinInterval(interval);
    }

    /**
     * @return the last known CellInfo
     */
    public List<CellInfo> getAllCellInfo() {
        return getServiceStateTracker().getAllCellInfo();
    }

    /**
     * @param workSource calling WorkSource
     * @param rspMsg the response message containing the cell info
     */
    public void requestCellInfoUpdate(WorkSource workSource, Message rspMsg) {
        getServiceStateTracker().requestAllCellInfo(workSource, rspMsg);
    }

    /**
     * Returns the current CellIdentity if known
     */
    public CellIdentity getCurrentCellIdentity() {
        return getServiceStateTracker().getCellIdentity();
    }

    /**
     * @param workSource calling WorkSource
     * @param rspMsg the response message containing the cell location
     */
    public void getCellIdentity(WorkSource workSource, Message rspMsg) {
        getServiceStateTracker().requestCellIdentity(workSource, rspMsg);
    }

    /**
     * Sets the minimum time in milli-seconds between {@link PhoneStateListener#onCellInfoChanged
     * PhoneStateListener.onCellInfoChanged} will be invoked.
     *
     * The default, 0, means invoke onCellInfoChanged when any of the reported
     * information changes. Setting the value to INT_MAX(0x7fffffff) means never issue
     * A onCellInfoChanged.
     *
     * @param rateInMillis the rate
     * @param workSource calling WorkSource
     */
    public void setCellInfoListRate(int rateInMillis, WorkSource workSource) {
        mCi.setCellInfoListRate(rateInMillis, null, workSource);
    }

    /**
     * Get voice message waiting indicator status. No change notification
     * available on this interface. Use PhoneStateNotifier or similar instead.
     *
     * @return true if there is a voice message waiting
     */
    public boolean getMessageWaitingIndicator() {
        return mVmCount != 0;
    }

    /**
     *  Retrieves manually selected network info.
     */
    public String getManualNetworkSelectionPlmn() {
        return null;
    }


    private int getCallForwardingIndicatorFromSharedPref() {
        int status = IccRecords.CALL_FORWARDING_STATUS_DISABLED;
        int subId = getSubId();
        if (SubscriptionManager.isValidSubscriptionId(subId)) {
            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
            status = sp.getInt(CF_STATUS + subId, IccRecords.CALL_FORWARDING_STATUS_UNKNOWN);
            Rlog.d(LOG_TAG, "getCallForwardingIndicatorFromSharedPref: for subId " + subId + "= " +
                    status);
            // Check for old preference if status is UNKNOWN for current subId. This part of the
            // code is needed only when upgrading from M to N.
            if (status == IccRecords.CALL_FORWARDING_STATUS_UNKNOWN) {
                String subscriberId = sp.getString(CF_ID, null);
                if (subscriberId != null) {
                    String currentSubscriberId = getSubscriberId();

                    if (subscriberId.equals(currentSubscriberId)) {
                        // get call forwarding status from preferences
                        status = sp.getInt(CF_STATUS, IccRecords.CALL_FORWARDING_STATUS_DISABLED);
                        setCallForwardingIndicatorInSharedPref(
                                status == IccRecords.CALL_FORWARDING_STATUS_ENABLED ? true : false);
                        Rlog.d(LOG_TAG, "getCallForwardingIndicatorFromSharedPref: " + status);
                    } else {
                        Rlog.d(LOG_TAG, "getCallForwardingIndicatorFromSharedPref: returning " +
                                "DISABLED as status for matching subscriberId not found");
                    }

                    // get rid of old preferences.
                    SharedPreferences.Editor editor = sp.edit();
                    editor.remove(CF_ID);
                    editor.remove(CF_STATUS);
                    editor.apply();
                }
            }
        } else {
            Rlog.e(LOG_TAG, "getCallForwardingIndicatorFromSharedPref: invalid subId " + subId);
        }
        return status;
    }

    private void setCallForwardingIndicatorInSharedPref(boolean enable) {
        int status = enable ? IccRecords.CALL_FORWARDING_STATUS_ENABLED :
                IccRecords.CALL_FORWARDING_STATUS_DISABLED;
        int subId = getSubId();
        Rlog.i(LOG_TAG, "setCallForwardingIndicatorInSharedPref: Storing status = " + status +
                " in pref " + CF_STATUS + subId);

        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
        SharedPreferences.Editor editor = sp.edit();
        editor.putInt(CF_STATUS + subId, status);
        editor.apply();
    }

    public void setVoiceCallForwardingFlag(int line, boolean enable, String number) {
        setCallForwardingIndicatorInSharedPref(enable);
        IccRecords r = getIccRecords();
        if (r != null) {
            r.setVoiceCallForwardingFlag(line, enable, number);
        }
        notifyCallForwardingIndicator();
    }

    /**
     * Set the voice call forwarding flag for GSM/UMTS and the like SIMs
     *
     * @param r to enable/disable
     * @param line to enable/disable
     * @param enable
     * @param number to which CFU is enabled
     */
    public void setVoiceCallForwardingFlag(IccRecords r, int line, boolean enable,
                                              String number) {
        setCallForwardingIndicatorInSharedPref(enable);
        if (r != null) {
            r.setVoiceCallForwardingFlag(line, enable, number);
        }
        notifyCallForwardingIndicator();
    }

    /**
     * Get voice call forwarding indicator status. No change notification
     * available on this interface. Use PhoneStateNotifier or similar instead.
     *
     * @return true if there is a voice call forwarding
     */
    public boolean getCallForwardingIndicator() {
        if (getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
            Rlog.e(LOG_TAG, "getCallForwardingIndicator: not possible in CDMA");
            return false;
        }
        IccRecords r = getIccRecords();
        int callForwardingIndicator = IccRecords.CALL_FORWARDING_STATUS_UNKNOWN;
        if (r != null) {
            callForwardingIndicator = r.getVoiceCallForwardingFlag();
        }
        if (callForwardingIndicator == IccRecords.CALL_FORWARDING_STATUS_UNKNOWN) {
            callForwardingIndicator = getCallForwardingIndicatorFromSharedPref();
        }
        Rlog.v(LOG_TAG, "getCallForwardingIndicator: iccForwardingFlag=" + (r != null
                    ? r.getVoiceCallForwardingFlag() : "null") + ", sharedPrefFlag="
                    + getCallForwardingIndicatorFromSharedPref());
        return (callForwardingIndicator == IccRecords.CALL_FORWARDING_STATUS_ENABLED);
    }

    public CarrierSignalAgent getCarrierSignalAgent() {
        return mCarrierSignalAgent;
    }

    public CarrierActionAgent getCarrierActionAgent() {
        return mCarrierActionAgent;
    }

    /**
     * Query the CDMA roaming preference setting.
     *
     * @param response is callback message to report one of TelephonyManager#CDMA_ROAMING_MODE_*
     */
    public void queryCdmaRoamingPreference(Message response) {
        mCi.queryCdmaRoamingPreference(response);
    }

    /**
     * Get the CDMA subscription mode setting.
     *
     * @param response is callback message to report one of TelephonyManager#CDMA_SUBSCRIPTION_*
     */
    public void queryCdmaSubscriptionMode(Message response) {
        mCi.getCdmaSubscriptionSource(response);
    }

    /**
     * Get current signal strength. No change notification available on this
     * interface. Use <code>PhoneStateNotifier</code> or an equivalent.
     * An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
     * The following special values are defined:</p>
     * <ul><li>0 means "-113 dBm or less".</li>
     * <li>31 means "-51 dBm or greater".</li></ul>
     *
     * @return Current signal strength as SignalStrength
     */
    public @NonNull SignalStrength getSignalStrength() {
        SignalStrengthController ssc = getSignalStrengthController();
        if (ssc == null) {
            return new SignalStrength();
        } else {
            return ssc.getSignalStrength();
        }
    }

    /**
     * @return true, if the device is in a state where both voice and data
     * are supported simultaneously. This can change based on location or network condition.
     */
    public boolean isConcurrentVoiceAndDataAllowed() {
        ServiceStateTracker sst = getServiceStateTracker();
        return sst == null ? false : sst.isConcurrentVoiceAndDataAllowed();
    }

    /**
     * Requests to set the CDMA roaming preference
     * @param cdmaRoamingType one of TelephonyManager#CDMA_ROAMING_MODE_*
     * @param response is callback message
     */
    public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) {
        mCi.setCdmaRoamingPreference(cdmaRoamingType, response);
    }

    /**
     * Requests to set the CDMA subscription mode
     * @param cdmaSubscriptionType one of TelephonyManager#CDMA_SUBSCRIPTION_*
     * @param response is callback message
     */
    public void setCdmaSubscriptionMode(int cdmaSubscriptionType, Message response) {
        mCi.setCdmaSubscriptionSource(cdmaSubscriptionType, response);
    }

    /**
     * Get the effective allowed network types on the device.
     *
     * @return effective network type
     */
    private @TelephonyManager.NetworkTypeBitMask long getEffectiveAllowedNetworkTypes() {
        long allowedNetworkTypes = TelephonyManager.getAllNetworkTypesBitmask();
        synchronized (mAllowedNetworkTypesForReasons) {
            for (long networkTypes : mAllowedNetworkTypesForReasons.values()) {
                allowedNetworkTypes = allowedNetworkTypes & networkTypes;
            }
        }
        if (!mIsCarrierNrSupported) {
            allowedNetworkTypes &= ~TelephonyManager.NETWORK_TYPE_BITMASK_NR;
        }
        if (m2gAdminUpdater.isCellular2gDisabled()) {
            logd("SubId " + getSubId()
                    + " disabling 2g in getEffectiveAllowedNetworkTypes according to admin user "
                    + "restriction");
            allowedNetworkTypes &= ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
        }
        logd("SubId" + getSubId() + ",getEffectiveAllowedNetworkTypes: "
                + TelephonyManager.convertNetworkTypeBitmaskToString(allowedNetworkTypes));
        return allowedNetworkTypes;
    }

    /**
     * Notify the latest allowed network types changed.
     */
    public void notifyAllowedNetworkTypesChanged(
            @TelephonyManager.AllowedNetworkTypesReason int reason) {
        logd("SubId" + getSubId() + ",notifyAllowedNetworkTypesChanged: reason: " + reason
                + " value:" + TelephonyManager.convertNetworkTypeBitmaskToString(
                getAllowedNetworkTypes(reason)));
        mNotifier.notifyAllowedNetworkTypesChanged(this, reason, getAllowedNetworkTypes(reason));
    }

    /**
     * Is E-UTRA-NR Dual Connectivity enabled
     */
    public void isNrDualConnectivityEnabled(Message message, WorkSource workSource) {
        mCi.isNrDualConnectivityEnabled(message, workSource);
    }

    /**
     * Enable/Disable E-UTRA-NR Dual Connectivity
     * @param nrDualConnectivityState expected NR dual connectivity state
     * This can be passed following states
     * <ol>
     * <li>Enable NR dual connectivity {@link TelephonyManager#NR_DUAL_CONNECTIVITY_ENABLE}
     * <li>Disable NR dual connectivity {@link TelephonyManager#NR_DUAL_CONNECTIVITY_DISABLE}
     * <li>Disable NR dual connectivity and force secondary cell to be released
     * {@link TelephonyManager#NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE}
     * </ol>
     */
    public void setNrDualConnectivityState(
            @TelephonyManager.NrDualConnectivityState int nrDualConnectivityState,
            Message message, WorkSource workSource) {
        mCi.setNrDualConnectivityState(nrDualConnectivityState, message, workSource);
    }

    /**
     * Get the allowed network types for a certain reason.
     * @param reason reason to configure allowed network types
     * @return the allowed network types.
     */
    public @TelephonyManager.NetworkTypeBitMask long getAllowedNetworkTypes(
            @TelephonyManager.AllowedNetworkTypesReason int reason) {
        long allowedNetworkTypes;
        long defaultAllowedNetworkTypes = RadioAccessFamily.getRafFromNetworkType(
                RILConstants.PREFERRED_NETWORK_MODE);

        if (!TelephonyManager.isValidAllowedNetworkTypesReason(reason)) {
            throw new IllegalArgumentException("AllowedNetworkTypes NumberFormat exception");
        }

        synchronized (mAllowedNetworkTypesForReasons) {
            allowedNetworkTypes = mAllowedNetworkTypesForReasons.getOrDefault(
                    reason,
                    defaultAllowedNetworkTypes);
        }
        if (!mIsCarrierNrSupported
                && reason == TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER) {
            allowedNetworkTypes = updateAllowedNetworkTypeForCarrierWithCarrierConfig();
        }
        logd("SubId" + getSubId() + ",get allowed network types "
                + convertAllowedNetworkTypeMapIndexToDbName(reason)
                + ": value = " + TelephonyManager.convertNetworkTypeBitmaskToString(
                allowedNetworkTypes));
        return allowedNetworkTypes;
    }

    /**
     * Loads the allowed network type from subscription database.
     */
    public void loadAllowedNetworksFromSubscriptionDatabase() {
        String result = null;
        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
                .getSubscriptionInfoInternal(getSubId());
        if (subInfo != null) {
            result = subInfo.getAllowedNetworkTypesForReasons();
        }

        // After fw load network type from DB, do unlock if subId is valid.
        mIsAllowedNetworkTypesLoadedFromDb = SubscriptionManager.isValidSubscriptionId(
                getSubId());

        if (TextUtils.isEmpty(result)) {
            return;
        }

        logd("SubId" + getSubId() + ",load allowed network types : value = " + result);
        Map<Integer, Long> oldAllowedNetworkTypes = new HashMap<>(mAllowedNetworkTypesForReasons);
        mAllowedNetworkTypesForReasons.clear();
        try {
            // Format: "REASON=VALUE,REASON2=VALUE2"
            for (String pair : result.trim().split(",")) {
                String[] networkTypesValues = (pair.trim().toLowerCase(Locale.ROOT)).split("=");
                if (networkTypesValues.length != 2) {
                    Rlog.e(LOG_TAG, "Invalid ALLOWED_NETWORK_TYPES from DB, value = " + pair);
                    continue;
                }
                int key = convertAllowedNetworkTypeDbNameToMapIndex(networkTypesValues[0]);
                long value = Long.parseLong(networkTypesValues[1]);
                if (TelephonyManager.isValidAllowedNetworkTypesReason(key)
                        && value != INVALID_ALLOWED_NETWORK_TYPES) {
                    synchronized (mAllowedNetworkTypesForReasons) {
                        mAllowedNetworkTypesForReasons.put(key, value);
                    }
                    if (!oldAllowedNetworkTypes.containsKey(key)
                            || oldAllowedNetworkTypes.get(key) != value) {
                        if (oldAllowedNetworkTypes.containsKey(key)) {
                            oldAllowedNetworkTypes.remove(key);
                        }
                        notifyAllowedNetworkTypesChanged(key);
                    }
                }
            }
        } catch (NumberFormatException e) {
            Rlog.e(LOG_TAG, "allowedNetworkTypes NumberFormat exception" + e);
        }

        for (int key : oldAllowedNetworkTypes.keySet()) {
            notifyAllowedNetworkTypesChanged(key);
        }
    }

    private int convertAllowedNetworkTypeDbNameToMapIndex(String name) {
        switch (name) {
            case ALLOWED_NETWORK_TYPES_TEXT_USER:
                return TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER;
            case ALLOWED_NETWORK_TYPES_TEXT_POWER:
                return TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER;
            case ALLOWED_NETWORK_TYPES_TEXT_CARRIER:
                return TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER;
            case ALLOWED_NETWORK_TYPES_TEXT_ENABLE_2G:
                return TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G;
            default:
                return INVALID_ALLOWED_NETWORK_TYPES;
        }
    }

    /**
     * Convert the allowed network types reason to string.
     *
     * @param reason The allowed network types reason.
     *
     * @return The converted string.
     */
    public static String convertAllowedNetworkTypeMapIndexToDbName(
            @TelephonyManager.AllowedNetworkTypesReason int reason) {
        switch (reason) {
            case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER:
                return ALLOWED_NETWORK_TYPES_TEXT_USER;
            case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER:
                return ALLOWED_NETWORK_TYPES_TEXT_POWER;
            case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER:
                return ALLOWED_NETWORK_TYPES_TEXT_CARRIER;
            case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
                return ALLOWED_NETWORK_TYPES_TEXT_ENABLE_2G;
            default:
                throw new IllegalArgumentException(
                        "No DB name conversion available for allowed network type reason: " + reason
                                + ". Did you forget to add an ALLOWED_NETWORK_TYPE_TEXT entry for"
                                + " a new reason?");
        }
    }

    private @TelephonyManager.NetworkTypeBitMask long
            updateAllowedNetworkTypeForCarrierWithCarrierConfig() {
        long defaultAllowedNetworkTypes = RadioAccessFamily.getRafFromNetworkType(
                RILConstants.PREFERRED_NETWORK_MODE);
        long allowedNetworkTypes;
        synchronized (mAllowedNetworkTypesForReasons) {
            allowedNetworkTypes = mAllowedNetworkTypesForReasons.getOrDefault(
                    TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER,
                    defaultAllowedNetworkTypes);
        }
        if (mIsCarrierNrSupported) {
            return allowedNetworkTypes;
        }
        allowedNetworkTypes = allowedNetworkTypes & ~TelephonyManager.NETWORK_TYPE_BITMASK_NR;

        logd("Allowed network types for 'carrier' reason is changed by carrier config = "
                + TelephonyManager.convertNetworkTypeBitmaskToString(allowedNetworkTypes));
        return allowedNetworkTypes;
    }

    /**
     * Requests to set the allowed network types for a specific reason
     *
     * @param reason reason to configure allowed network type
     * @param networkTypes one of the network types
     * @param response callback Message
     */
    public void setAllowedNetworkTypes(@TelephonyManager.AllowedNetworkTypesReason int reason,
            @TelephonyManager.NetworkTypeBitMask long networkTypes, @Nullable Message response) {
        int subId = getSubId();
        if (!TelephonyManager.isValidAllowedNetworkTypesReason(reason)) {
            loge("setAllowedNetworkTypes: Invalid allowed network type reason: " + reason);
            if (response != null) {
                AsyncResult.forMessage(response, null,
                        new CommandException(CommandException.Error.INVALID_ARGUMENTS));
                response.sendToTarget();
            }
            return;
        }
        if (!SubscriptionManager.isUsableSubscriptionId(subId)
                || !mIsAllowedNetworkTypesLoadedFromDb) {
            loge("setAllowedNetworkTypes: no sim or network type is not loaded. SubscriptionId: "
                    + subId + ", isNetworkTypeLoaded" + mIsAllowedNetworkTypesLoadedFromDb);
            if (response != null) {
                AsyncResult.forMessage(response, null,
                        new CommandException(CommandException.Error.MISSING_RESOURCE));
                response.sendToTarget();
            }
            return;
        }
        String mapAsString = "";
        synchronized (mAllowedNetworkTypesForReasons) {
            mAllowedNetworkTypesForReasons.put(reason, networkTypes);
            mapAsString = mAllowedNetworkTypesForReasons.keySet().stream()
                    .map(key -> convertAllowedNetworkTypeMapIndexToDbName(key) + "="
                            + mAllowedNetworkTypesForReasons.get(key))
                    .collect(Collectors.joining(","));
        }
        SubscriptionManager.setSubscriptionProperty(subId,
                SubscriptionManager.ALLOWED_NETWORK_TYPES,
                mapAsString);
        logd("setAllowedNetworkTypes: SubId" + subId + ",setAllowedNetworkTypes " + mapAsString);

        updateAllowedNetworkTypes(response);
        notifyAllowedNetworkTypesChanged(reason);
    }

    protected void updateAllowedNetworkTypes(Message response) {
        int modemRaf = getRadioAccessFamily();
        if (modemRaf == RadioAccessFamily.RAF_UNKNOWN) {
            Rlog.d(LOG_TAG, "setPreferredNetworkType: Abort, unknown RAF: "
                    + modemRaf);
            if (response != null) {
                CommandException ex;
                ex = new CommandException(CommandException.Error.GENERIC_FAILURE);
                AsyncResult.forMessage(response, null, ex);
                response.sendToTarget();
            }
            return;
        }

        int filteredRaf = (int) (modemRaf & getEffectiveAllowedNetworkTypes());

        logd("setAllowedNetworkTypes: modemRafBitMask = " + modemRaf
                + " ,modemRaf = " + TelephonyManager.convertNetworkTypeBitmaskToString(modemRaf)
                + " ,filteredRafBitMask = " + filteredRaf
                + " ,filteredRaf = " + TelephonyManager.convertNetworkTypeBitmaskToString(
                filteredRaf));
        mCi.setAllowedNetworkTypesBitmap(filteredRaf, response);
        mPreferredNetworkTypeRegistrants.notifyRegistrants();
    }

    /**
     * Query the allowed network types bitmask setting
     *
     * @param response is callback message to report network types bitmask
     */
    public void getAllowedNetworkTypesBitmask(Message response) {
        mCi.getAllowedNetworkTypesBitmap(response);
    }

    /**
     * Register for preferred network type changes
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForPreferredNetworkTypeChanged(Handler h, int what, Object obj) {
        checkCorrectThread(h);
        mPreferredNetworkTypeRegistrants.addUnique(h, what, obj);
    }

    /**
     * Unregister for preferred network type changes.
     *
     * @param h Handler that should be unregistered.
     */
    public void unregisterForPreferredNetworkTypeChanged(Handler h) {
        mPreferredNetworkTypeRegistrants.remove(h);
    }

    /**
     * Get the cached value of the preferred network type setting
     */
    public int getCachedAllowedNetworkTypesBitmask() {
        if (mCi != null && mCi instanceof BaseCommands) {
            return ((BaseCommands) mCi).mAllowedNetworkTypesBitmask;
        } else {
            return RadioAccessFamily.getRafFromNetworkType(RILConstants.PREFERRED_NETWORK_MODE);
        }
    }

    /**
     * Gets the default SMSC address.
     *
     * @param result Callback message contains the SMSC address.
     */
    @UnsupportedAppUsage
    public void getSmscAddress(Message result) {
        mCi.getSmscAddress(result);
    }

    /**
     * Sets the default SMSC address.
     *
     * @param address new SMSC address
     * @param result Callback message is empty on completion
     */
    @UnsupportedAppUsage
    public void setSmscAddress(String address, Message result) {
        mCi.setSmscAddress(address, result);
    }

    /**
     * setTTYMode
     * sets a TTY mode option.
     * @param ttyMode is a one of the following:
     * - {@link com.android.internal.telephony.Phone#TTY_MODE_OFF}
     * - {@link com.android.internal.telephony.Phone#TTY_MODE_FULL}
     * - {@link com.android.internal.telephony.Phone#TTY_MODE_HCO}
     * - {@link com.android.internal.telephony.Phone#TTY_MODE_VCO}
     * @param onComplete a callback message when the action is completed
     */
    public void setTTYMode(int ttyMode, Message onComplete) {
        mCi.setTTYMode(ttyMode, onComplete);
    }

    /**
     * setUiTTYMode
     * sets a TTY mode option.
     * @param ttyMode is a one of the following:
     * - {@link com.android.internal.telephony.Phone#TTY_MODE_OFF}
     * - {@link com.android.internal.telephony.Phone#TTY_MODE_FULL}
     * - {@link com.android.internal.telephony.Phone#TTY_MODE_HCO}
     * - {@link com.android.internal.telephony.Phone#TTY_MODE_VCO}
     * @param onComplete a callback message when the action is completed
     */
    public void setUiTTYMode(int uiTtyMode, Message onComplete) {
        Rlog.d(LOG_TAG, "unexpected setUiTTYMode method call");
    }

    /**
     * queryTTYMode
     * query the status of the TTY mode
     *
     * @param onComplete a callback message when the action is completed.
     */
    public void queryTTYMode(Message onComplete) {
        mCi.queryTTYMode(onComplete);
    }

    /**
     * Enable or disable enhanced Voice Privacy (VP). If enhanced VP is
     * disabled, normal VP is enabled.
     *
     * @param enable whether true or false to enable or disable.
     * @param onComplete a callback message when the action is completed.
     */
    public void enableEnhancedVoicePrivacy(boolean enable, Message onComplete) {
    }

    /**
     * Get the currently set Voice Privacy (VP) mode.
     *
     * @param onComplete a callback message when the action is completed.
     */
    public void getEnhancedVoicePrivacy(Message onComplete) {
    }

    /**
     * Assign a specified band for RF configuration.
     *
     * @param bandMode one of BM_*_BAND
     * @param response is callback message
     */
    public void setBandMode(int bandMode, Message response) {
        mCi.setBandMode(bandMode, response);
    }

    /**
     * Query the list of band mode supported by RF.
     *
     * @param response is callback message
     *        ((AsyncResult)response.obj).result  is an int[] where int[0] is
     *        the size of the array and the rest of each element representing
     *        one available BM_*_BAND
     */
    public void queryAvailableBandMode(Message response) {
        mCi.queryAvailableBandMode(response);
    }

    /**
     * Read one of the NV items defined in {@link RadioNVItems} / {@code ril_nv_items.h}.
     * Used for device configuration by some CDMA operators.
     *
     * @param itemID the ID of the item to read
     * @param response callback message with the String response in the obj field
     * @param workSource calling WorkSource
     */
    public void nvReadItem(int itemID, Message response, WorkSource workSource) {
        mCi.nvReadItem(itemID, response, workSource);
    }

    /**
     * Write one of the NV items defined in {@link RadioNVItems} / {@code ril_nv_items.h}.
     * Used for device configuration by some CDMA operators.
     *
     * @param itemID the ID of the item to read
     * @param itemValue the value to write, as a String
     * @param response Callback message.
     * @param workSource calling WorkSource
     */
    public void nvWriteItem(int itemID, String itemValue, Message response,
            WorkSource workSource) {
        mCi.nvWriteItem(itemID, itemValue, response, workSource);
    }

    /**
     * Update the CDMA Preferred Roaming List (PRL) in the radio NV storage.
     * Used for device configuration by some CDMA operators.
     *
     * @param preferredRoamingList byte array containing the new PRL
     * @param response Callback message.
     */
    public void nvWriteCdmaPrl(byte[] preferredRoamingList, Message response) {
        mCi.nvWriteCdmaPrl(preferredRoamingList, response);
    }

    /**
     * Perform the radio modem reboot. The radio will be taken offline. Used for device
     * configuration by some CDMA operators.
     * TODO: reuse nvResetConfig for now, should move to separate HAL API.
     *
     * @param response Callback message.
     */
    public void rebootModem(Message response) {
        mCi.nvResetConfig(1 /* 1: reload NV reset, trigger a modem reboot */, response);
    }

    /**
     * Perform the modem configuration reset. Used for device configuration by some CDMA operators.
     * TODO: reuse nvResetConfig for now, should move to separate HAL API.
     *
     * @param response Callback message.
     */
    public void resetModemConfig(Message response) {
        mCi.nvResetConfig(3 /* factory NV reset */, response);
    }

    /**
     * Perform modem configuration erase. Used for network reset
     *
     * @param response Callback message.
     */
    public void eraseModemConfig(Message response) {
        mCi.nvResetConfig(2 /* erase NV */, response);
    }

    /**
     * Erase data saved in the SharedPreference. Used for network reset
     *
     */
    public boolean eraseDataInSharedPreferences() {
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
        SharedPreferences.Editor editor = sp.edit();
        Rlog.d(LOG_TAG, "Erase all data saved in SharedPreferences");
        editor.clear();
        return editor.commit();
    }

    public void setSystemSelectionChannels(List<RadioAccessSpecifier> specifiers,
            Message response) {
        mCi.setSystemSelectionChannels(specifiers, response);
    }

    /**
     * Get which bands the modem's background scan is acting on.
     *
     * @param response Callback message.
     */
    public void getSystemSelectionChannels(Message response) {
        mCi.getSystemSelectionChannels(response);
    }

    public void notifyDataActivity() {
        mNotifier.notifyDataActivity(this);
    }

    private void notifyMessageWaitingIndicator() {
        // Do not notify voice mail waiting if device doesn't support voice
        if (!mIsVoiceCapable)
            return;

        // This function is added to send the notification to DefaultPhoneNotifier.
        mNotifier.notifyMessageWaitingChanged(this);
    }

    /** Send notification with an updated PreciseDataConnectionState to a single data connection */
    public void notifyDataConnection(PreciseDataConnectionState state) {
        mNotifier.notifyDataConnection(this, state);
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void notifyOtaspChanged(int otaspMode) {
        mOtaspRegistrants.notifyRegistrants(new AsyncResult(null, otaspMode, null));
    }

    public void notifyVoiceActivationStateChanged(int state) {
        mNotifier.notifyVoiceActivationStateChanged(this, state);
    }

    public void notifyDataActivationStateChanged(int state) {
        mNotifier.notifyDataActivationStateChanged(this, state);
    }

    public void notifyUserMobileDataStateChanged(boolean state) {
        mNotifier.notifyUserMobileDataStateChanged(this, state);
    }

    /** Send notification that display info has changed. */
    public void notifyDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
        mNotifier.notifyDisplayInfoChanged(this, telephonyDisplayInfo);
    }

    public void notifySignalStrength() {
        mNotifier.notifySignalStrength(this);
    }

    public PhoneConstants.DataState getDataConnectionState(String apnType) {
        return PhoneConstants.DataState.DISCONNECTED;
    }

    /** Default implementation to get the PreciseDataConnectionState */
    public @Nullable PreciseDataConnectionState getPreciseDataConnectionState(String apnType) {
        return null;
    }

    public void notifyCellInfo(List<CellInfo> cellInfo) {
        AsyncResult ar = new AsyncResult(null, cellInfo, null);
        mCellInfoRegistrants.notifyRegistrants(ar);

        mNotifier.notifyCellInfo(this, cellInfo);
    }

    /**
     * Registration point for PhysicalChannelConfig change.
     * @param h handler to notify
     * @param what what code of message when delivered
     * @param obj placed in Message.obj.userObj
     */
    public void registerForPhysicalChannelConfig(Handler h, int what, Object obj) {
        checkCorrectThread(h);
        Registrant registrant = new Registrant(h, what, obj);
        mPhysicalChannelConfigRegistrants.add(registrant);
        // notify first
        List<PhysicalChannelConfig> physicalChannelConfigs = getPhysicalChannelConfigList();
        if (physicalChannelConfigs != null) {
            registrant.notifyRegistrant(new AsyncResult(null, physicalChannelConfigs, null));
        }
    }

    public void unregisterForPhysicalChannelConfig(Handler h) {
        mPhysicalChannelConfigRegistrants.remove(h);
    }

    /** Notify {@link PhysicalChannelConfig} changes. */
    public void notifyPhysicalChannelConfig(List<PhysicalChannelConfig> configs) {
        mPhysicalChannelConfigRegistrants.notifyRegistrants(new AsyncResult(null, configs, null));
        mNotifier.notifyPhysicalChannelConfig(this, configs);
    }

    public List<PhysicalChannelConfig> getPhysicalChannelConfigList() {
        return null;
    }

    /**
     * Notify listeners that SRVCC state has changed.
     */
    public void notifySrvccStateChanged(int state) {
        mNotifier.notifySrvccStateChanged(this, state);
    }

    /** Notify the {@link EmergencyNumber} changes. */
    public void notifyEmergencyNumberList() {
        mNotifier.notifyEmergencyNumberList(this);
    }

    /** Notify the outgoing Sms {@link EmergencyNumber} changes. */
    public void notifyOutgoingEmergencySms(EmergencyNumber emergencyNumber) {
        mNotifier.notifyOutgoingEmergencySms(this, emergencyNumber);
    }

    /** Notify the data enabled changes. */
    public void notifyDataEnabled(boolean enabled,
            @TelephonyManager.DataEnabledReason int reason) {
        mNotifier.notifyDataEnabled(this, enabled, reason);
    }

    /** Notify link capacity estimate has changed. */
    public void notifyLinkCapacityEstimateChanged(
            List<LinkCapacityEstimate> linkCapacityEstimateList) {
        mNotifier.notifyLinkCapacityEstimateChanged(this, linkCapacityEstimateList);
    }

    /**
     * @return true if a mobile originating emergency call is active
     */
    public boolean isInEmergencyCall() {
        return false;
    }

    // This property is used to handle phone process crashes, and is the same for CDMA and IMS
    // phones
    protected static boolean getInEcmMode() {
        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
            return EmergencyStateTracker.getInstance().isInEcm();
        }
        return TelephonyProperties.in_ecm_mode().orElse(false);
    }

    /**
     * @return {@code true} if we are in emergency call back mode. This is a period where the phone
     * should be using as little power as possible and be ready to receive an incoming call from the
     * emergency operator.
     */
    public boolean isInEcm() {
        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
            return EmergencyStateTracker.getInstance().isInEcm();
        }
        return mIsPhoneInEcmState;
    }

    public boolean isInImsEcm() {
        return false;
    }

    public boolean isInCdmaEcm() {
        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
            return EmergencyStateTracker.getInstance().isInCdmaEcm();
        }
        return getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA && isInEcm()
                && (mImsPhone == null || !mImsPhone.isInImsEcm());
    }

    public void setIsInEcm(boolean isInEcm) {
        if (!getUnitTestMode()) {
            TelephonyProperties.in_ecm_mode(isInEcm);
        }
        mIsPhoneInEcmState = isInEcm;
    }

    /**
     * @return true if this Phone is in an emergency call that caused emergency callback mode to be
     * canceled, false if not.
     */
    public boolean isEcmCanceledForEmergency() {
        return mEcmCanceledForEmergency;
    }

    /**
     * Set whether or not this Phone has an active emergency call that was placed during emergency
     * callback mode and caused it to be temporarily canceled.
     * @param isCanceled true if an emergency call was placed that caused ECM to be canceled, false
     *                   if it is not in this state.
     */
    public void setEcmCanceledForEmergency(boolean isCanceled) {
        mEcmCanceledForEmergency = isCanceled;
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private static int getVideoState(Call call) {
        int videoState = VideoProfile.STATE_AUDIO_ONLY;
        Connection conn = call.getEarliestConnection();
        if (conn != null) {
            videoState = conn.getVideoState();
        }
        return videoState;
    }

    /**
     * Determines if the specified call currently is or was at some point a video call, or if it is
     * a conference call.
     * @param call The call.
     * @return {@code true} if the call is or was a video call or is a conference call,
     *      {@code false} otherwise.
     */
    private boolean isVideoCallOrConference(Call call) {
        if (call.isMultiparty()) {
            return true;
        }

        boolean isDowngradedVideoCall = false;
        if (call instanceof ImsPhoneCall) {
            ImsPhoneCall imsPhoneCall = (ImsPhoneCall) call;
            ImsCall imsCall = imsPhoneCall.getImsCall();
            return imsCall != null && (imsCall.isVideoCall() ||
                    imsCall.wasVideoCall());
        }
        return isDowngradedVideoCall;
    }

    /**
     * @return {@code true} if an IMS video call or IMS conference is present, false otherwise.
     */
    public boolean isImsVideoCallOrConferencePresent() {
        boolean isPresent = false;
        if (mImsPhone != null) {
            isPresent = isVideoCallOrConference(mImsPhone.getForegroundCall()) ||
                    isVideoCallOrConference(mImsPhone.getBackgroundCall()) ||
                    isVideoCallOrConference(mImsPhone.getRingingCall());
        }
        Rlog.d(LOG_TAG, "isImsVideoCallOrConferencePresent: " + isPresent);
        return isPresent;
    }

    /**
     * Return a numerical identifier for the phone radio interface.
     * @return PHONE_TYPE_XXX as defined above.
     */
    @UnsupportedAppUsage
    public abstract int getPhoneType();

    /**
     * Returns unread voicemail count. This count is shown when the  voicemail
     * notification is expanded.<p>
     */
    public int getVoiceMessageCount(){
        return mVmCount;
    }

    /** sets the voice mail count of the phone and notifies listeners. */
    public void setVoiceMessageCount(int countWaiting) {
        mVmCount = countWaiting;
        int subId = getSubId();
        if (SubscriptionManager.isValidSubscriptionId(subId)) {

            Rlog.d(LOG_TAG, "setVoiceMessageCount: Storing Voice Mail Count = " + countWaiting +
                    " for mVmCountKey = " + VM_COUNT + subId + " in preferences.");

            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
            SharedPreferences.Editor editor = sp.edit();
            editor.putInt(VM_COUNT + subId, countWaiting);
            editor.apply();
        } else {
            Rlog.e(LOG_TAG, "setVoiceMessageCount in sharedPreference: invalid subId " + subId);
        }
        // store voice mail count in SIM
        IccRecords records = UiccController.getInstance().getIccRecords(
                mPhoneId, UiccController.APP_FAM_3GPP);
        if (records != null) {
            Rlog.d(LOG_TAG, "setVoiceMessageCount: updating SIM Records");
            records.setVoiceMessageWaiting(1, countWaiting);
        } else {
            Rlog.d(LOG_TAG, "setVoiceMessageCount: SIM Records not found");
        }
        // notify listeners of voice mail
        notifyMessageWaitingIndicator();
    }

    /** gets the voice mail count from preferences */
    protected int getStoredVoiceMessageCount() {
        int countVoiceMessages = 0;
        int subId = getSubId();
        if (SubscriptionManager.isValidSubscriptionId(subId)) {
            int invalidCount = -2;  //-1 is not really invalid. It is used for unknown number of vm
            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
            int countFromSP = sp.getInt(VM_COUNT + subId, invalidCount);
            if (countFromSP != invalidCount) {
                countVoiceMessages = countFromSP;
                Rlog.d(LOG_TAG, "getStoredVoiceMessageCount: from preference for subId " + subId +
                        "= " + countVoiceMessages);
            } else {
                // Check for old preference if count not found for current subId. This part of the
                // code is needed only when upgrading from M to N.
                String subscriberId = sp.getString(VM_ID, null);
                if (subscriberId != null) {
                    String currentSubscriberId = getSubscriberId();

                    if (currentSubscriberId != null && currentSubscriberId.equals(subscriberId)) {
                        // get voice mail count from preferences
                        countVoiceMessages = sp.getInt(VM_COUNT, 0);
                        setVoiceMessageCount(countVoiceMessages);
                        Rlog.d(LOG_TAG, "getStoredVoiceMessageCount: from preference = " +
                                countVoiceMessages);
                    } else {
                        Rlog.d(LOG_TAG, "getStoredVoiceMessageCount: returning 0 as count for " +
                                "matching subscriberId not found");

                    }
                    // get rid of old preferences.
                    SharedPreferences.Editor editor = sp.edit();
                    editor.remove(VM_ID);
                    editor.remove(VM_COUNT);
                    editor.apply();
                }
            }
        } else {
            Rlog.e(LOG_TAG, "getStoredVoiceMessageCount: invalid subId " + subId);
        }
        return countVoiceMessages;
    }

    /**
     * send secret dialer codes to launch arbitrary activities.
     * an Intent is started with the android_secret_code://<code> URI.
     *
     * @param code stripped version of secret code without *#*# prefix and #*#* suffix
     */
    public void sendDialerSpecialCode(String code) {
        if (!TextUtils.isEmpty(code)) {
            final BroadcastOptions options = BroadcastOptions.makeBasic();
            options.setBackgroundActivityStartsAllowed(true);
            Intent intent = new Intent(TelephonyIntents.SECRET_CODE_ACTION,
                    Uri.parse("android_secret_code://" + code));
            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
            mContext.sendBroadcast(intent, null, options.toBundle());

            // {@link TelephonyManager.ACTION_SECRET_CODE} will replace {@link
            // TelephonyIntents#SECRET_CODE_ACTION} in the next Android version. Before
            // that both of these two actions will be broadcast.
            Intent secrectCodeIntent = new Intent(TelephonyManager.ACTION_SECRET_CODE,
                    Uri.parse("android_secret_code://" + code));
            secrectCodeIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
            mContext.sendBroadcast(secrectCodeIntent, null, options.toBundle());
        }
    }

    /**
     * Returns the CDMA ERI icon index to display
     */
    public int getCdmaEriIconIndex() {
        return -1;
    }

    /**
     * Returns the CDMA ERI icon mode,
     * 0 - ON
     * 1 - FLASHING
     */
    public int getCdmaEriIconMode() {
        return -1;
    }

    /**
     * Returns the CDMA ERI text,
     */
    public String getCdmaEriText() {
        return "GSM nw, no ERI";
    }

    /**
     * Retrieves the MIN for CDMA phones.
     */
    public String getCdmaMin() {
        return null;
    }

    /**
     * Check if subscription data has been assigned to mMin
     *
     * return true if MIN info is ready; false otherwise.
     */
    public boolean isMinInfoReady() {
        return false;
    }

    /**
     *  Retrieves PRL Version for CDMA phones
     */
    public String getCdmaPrlVersion(){
        return null;
    }

    /**
     * @return {@code true} if data is suspended.
     */
    public boolean isDataSuspended() {
        return false;
    }

    /**
     * send burst DTMF tone, it can send the string as single character or multiple character
     * ignore if there is no active call or not valid digits string.
     * Valid digit means only includes characters ISO-LATIN characters 0-9, *, #
     * The difference between sendDtmf and sendBurstDtmf is sendDtmf only sends one character,
     * this api can send single character and multiple character, also, this api has response
     * back to caller.
     *
     * @param dtmfString is string representing the dialing digit(s) in the active call
     * @param on the DTMF ON length in milliseconds, or 0 for default
     * @param off the DTMF OFF length in milliseconds, or 0 for default
     * @param onComplete is the callback message when the action is processed by BP
     *
     */
    public void sendBurstDtmf(String dtmfString, int on, int off, Message onComplete) {
    }

    /**
     * Sets an event to be fired when the telephony system processes
     * a post-dial character on an outgoing call.<p>
     *
     * Messages of type <code>what</code> will be sent to <code>h</code>.
     * The <code>obj</code> field of these Message's will be instances of
     * <code>AsyncResult</code>. <code>Message.obj.result</code> will be
     * a Connection object.<p>
     *
     * Message.arg1 will be the post dial character being processed,
     * or 0 ('\0') if end of string.<p>
     *
     * If Connection.getPostDialState() == WAIT,
     * the application must call
     * {@link com.android.internal.telephony.Connection#proceedAfterWaitChar()
     * Connection.proceedAfterWaitChar()} or
     * {@link com.android.internal.telephony.Connection#cancelPostDial()
     * Connection.cancelPostDial()}
     * for the telephony system to continue playing the post-dial
     * DTMF sequence.<p>
     *
     * If Connection.getPostDialState() == WILD,
     * the application must call
     * {@link com.android.internal.telephony.Connection#proceedAfterWildChar
     * Connection.proceedAfterWildChar()}
     * or
     * {@link com.android.internal.telephony.Connection#cancelPostDial()
     * Connection.cancelPostDial()}
     * for the telephony system to continue playing the
     * post-dial DTMF sequence.<p>
     *
     * Only one post dial character handler may be set. <p>
     * Calling this method with "h" equal to null unsets this handler.<p>
     */
    @UnsupportedAppUsage
    public void setOnPostDialCharacter(Handler h, int what, Object obj) {
        mPostDialHandler = new Registrant(h, what, obj);
    }

    public Registrant getPostDialHandler() {
        return mPostDialHandler;
    }

    /**
     * request to exit emergency call back mode
     * the caller should use setOnECMModeExitResponse
     * to receive the emergency callback mode exit response
     */
    @UnsupportedAppUsage
    public void exitEmergencyCallbackMode() {
    }

    /**
     * Register for notifications when CDMA OTA Provision status change
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForCdmaOtaStatusChange(Handler h, int what, Object obj) {
    }

    /**
     * Unregister for notifications when CDMA OTA Provision status change
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForCdmaOtaStatusChange(Handler h) {
    }

    /**
     * Registration point for subscription info ready
     * @param h handler to notify
     * @param what what code of message when delivered
     * @param obj placed in Message.obj
     */
    public void registerForSubscriptionInfoReady(Handler h, int what, Object obj) {
    }

    /**
     * Unregister for notifications for subscription info
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForSubscriptionInfoReady(Handler h) {
    }

    /**
     * Returns true if OTA Service Provisioning needs to be performed.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public boolean needsOtaServiceProvisioning() {
        return false;
    }

    /**
     * this decides if the dial number is OTA(Over the air provision) number or not
     * @param dialStr is string representing the dialing digit(s)
     * @return  true means the dialStr is OTA number, and false means the dialStr is not OTA number
     */
    public  boolean isOtaSpNumber(String dialStr) {
        return false;
    }

    /**
     * Register for notifications when OTA Service Provisioning mode has changed.
     *
     * <p>The mode is integer. {@link TelephonyManager#OTASP_UNKNOWN}
     * means the value is currently unknown and the system should wait until
     * {@link TelephonyManager#OTASP_NEEDED} or {@link TelephonyManager#OTASP_NOT_NEEDED} is
     * received before making the decision to perform OTASP or not.
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForOtaspChange(Handler h, int what, Object obj) {
        checkCorrectThread(h);
        mOtaspRegistrants.addUnique(h, what, obj);
        // notify first
        new Registrant(h, what, obj).notifyRegistrant(new AsyncResult(null, getOtasp(), null));
    }

    /**
     * Unegister for notifications when OTA Service Provisioning mode has changed.
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForOtaspChange(Handler h) {
        mOtaspRegistrants.remove(h);
    }

    /**
     * Returns the current OTA Service Provisioning mode.
     *
     * @see registerForOtaspChange
     */
    public int getOtasp() {
        return TelephonyManager.OTASP_UNKNOWN;
    }

    /**
     * Register for notifications when CDMA call waiting comes
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForCallWaiting(Handler h, int what, Object obj){
    }

    /**
     * Unegister for notifications when CDMA Call waiting comes
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForCallWaiting(Handler h){
    }

    /**
     * Registration point for Ecm timer reset
     * @param h handler to notify
     * @param what user-defined message code
     * @param obj placed in Message.obj
     */
    @UnsupportedAppUsage
    public void registerForEcmTimerReset(Handler h, int what, Object obj) {
    }

    /**
     * Unregister for notification for Ecm timer reset
     * @param h Handler to be removed from the registrant list.
     */
    @UnsupportedAppUsage
    public void unregisterForEcmTimerReset(Handler h) {
    }

    /**
     * Register for signal information notifications from the network.
     * Message.obj will contain an AsyncResult.
     * AsyncResult.result will be a SuppServiceNotification instance.
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForSignalInfo(Handler h, int what, Object obj) {
        mCi.registerForSignalInfo(h, what, obj);
    }

    /**
     * Unregisters for signal information notifications.
     * Extraneous calls are tolerated silently
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForSignalInfo(Handler h) {
        mCi.unregisterForSignalInfo(h);
    }

    /**
     * Register for display information notifications from the network.
     * Message.obj will contain an AsyncResult.
     * AsyncResult.result will be a SuppServiceNotification instance.
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForDisplayInfo(Handler h, int what, Object obj) {
        mCi.registerForDisplayInfo(h, what, obj);
    }

    /**
     * Unregisters for display information notifications.
     * Extraneous calls are tolerated silently
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForDisplayInfo(Handler h) {
         mCi.unregisterForDisplayInfo(h);
    }

    /**
     * Register for CDMA number information record notification from the network.
     * Message.obj will contain an AsyncResult.
     * AsyncResult.result will be a CdmaInformationRecords.CdmaNumberInfoRec
     * instance.
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForNumberInfo(Handler h, int what, Object obj) {
        mCi.registerForNumberInfo(h, what, obj);
    }

    /**
     * Unregisters for number information record notifications.
     * Extraneous calls are tolerated silently
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForNumberInfo(Handler h) {
        mCi.unregisterForNumberInfo(h);
    }

    /**
     * Register for CDMA redirected number information record notification
     * from the network.
     * Message.obj will contain an AsyncResult.
     * AsyncResult.result will be a CdmaInformationRecords.CdmaRedirectingNumberInfoRec
     * instance.
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForRedirectedNumberInfo(Handler h, int what, Object obj) {
        mCi.registerForRedirectedNumberInfo(h, what, obj);
    }

    /**
     * Unregisters for redirected number information record notification.
     * Extraneous calls are tolerated silently
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForRedirectedNumberInfo(Handler h) {
        mCi.unregisterForRedirectedNumberInfo(h);
    }

    /**
     * Register for CDMA line control information record notification
     * from the network.
     * Message.obj will contain an AsyncResult.
     * AsyncResult.result will be a CdmaInformationRecords.CdmaLineControlInfoRec
     * instance.
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForLineControlInfo(Handler h, int what, Object obj) {
        mCi.registerForLineControlInfo(h, what, obj);
    }

    /**
     * Unregisters for line control information notifications.
     * Extraneous calls are tolerated silently
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForLineControlInfo(Handler h) {
        mCi.unregisterForLineControlInfo(h);
    }

    /**
     * Register for CDMA T53 CLIR information record notifications
     * from the network.
     * Message.obj will contain an AsyncResult.
     * AsyncResult.result will be a CdmaInformationRecords.CdmaT53ClirInfoRec
     * instance.
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerFoT53ClirlInfo(Handler h, int what, Object obj) {
        mCi.registerFoT53ClirlInfo(h, what, obj);
    }

    /**
     * Unregisters for T53 CLIR information record notification
     * Extraneous calls are tolerated silently
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForT53ClirInfo(Handler h) {
        mCi.unregisterForT53ClirInfo(h);
    }

    /**
     * Register for CDMA T53 audio control information record notifications
     * from the network.
     * Message.obj will contain an AsyncResult.
     * AsyncResult.result will be a CdmaInformationRecords.CdmaT53AudioControlInfoRec
     * instance.
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForT53AudioControlInfo(Handler h, int what, Object obj) {
        mCi.registerForT53AudioControlInfo(h, what, obj);
    }

    /**
     * Unregisters for T53 audio control information record notifications.
     * Extraneous calls are tolerated silently
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForT53AudioControlInfo(Handler h) {
        mCi.unregisterForT53AudioControlInfo(h);
    }

    /**
     * registers for exit emergency call back mode request response
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    @UnsupportedAppUsage
    public void setOnEcbModeExitResponse(Handler h, int what, Object obj){
    }

    /**
     * Unregisters for exit emergency call back mode request response
     *
     * @param h Handler to be removed from the registrant list.
     */
    @UnsupportedAppUsage
    public void unsetOnEcbModeExitResponse(Handler h){
    }

    /**
     * Register for radio off or not available
     *
     * @param h Handler that receives the notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForRadioOffOrNotAvailable(Handler h, int what, Object obj) {
        mRadioOffOrNotAvailableRegistrants.addUnique(h, what, obj);
    }

    /**
     * Unregisters for radio off or not available
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForRadioOffOrNotAvailable(Handler h) {
        mRadioOffOrNotAvailableRegistrants.remove(h);
    }

    /**
     *  Location to an updatable file listing carrier provisioning urls.
     *  An example:
     *
     * <?xml version="1.0" encoding="utf-8"?>
     *  <provisioningUrls>
     *   <provisioningUrl mcc="310" mnc="4">http://myserver.com/foo?mdn=%3$s&amp;iccid=%1$s&amp;imei=%2$s</provisioningUrl>
     *  </provisioningUrls>
     */
    private static final String PROVISIONING_URL_PATH =
            "/data/misc/radio/provisioning_urls.xml";
    private final File mProvisioningUrlFile = new File(PROVISIONING_URL_PATH);

    /** XML tag for root element. */
    private static final String TAG_PROVISIONING_URLS = "provisioningUrls";
    /** XML tag for individual url */
    private static final String TAG_PROVISIONING_URL = "provisioningUrl";
    /** XML attribute for mcc */
    private static final String ATTR_MCC = "mcc";
    /** XML attribute for mnc */
    private static final String ATTR_MNC = "mnc";

    private String getProvisioningUrlBaseFromFile() {
        XmlPullParser parser;
        final Configuration config = mContext.getResources().getConfiguration();

        try (FileReader fileReader = new FileReader(mProvisioningUrlFile)) {
            parser = Xml.newPullParser();
            parser.setInput(fileReader);
            XmlUtils.beginDocument(parser, TAG_PROVISIONING_URLS);

            while (true) {
                XmlUtils.nextElement(parser);

                final String element = parser.getName();
                if (element == null) break;

                if (element.equals(TAG_PROVISIONING_URL)) {
                    String mcc = parser.getAttributeValue(null, ATTR_MCC);
                    try {
                        if (mcc != null && Integer.parseInt(mcc) == config.mcc) {
                            String mnc = parser.getAttributeValue(null, ATTR_MNC);
                            if (mnc != null && Integer.parseInt(mnc) == config.mnc) {
                                parser.next();
                                if (parser.getEventType() == XmlPullParser.TEXT) {
                                    return parser.getText();
                                }
                            }
                        }
                    } catch (NumberFormatException e) {
                        Rlog.e(LOG_TAG, "Exception in getProvisioningUrlBaseFromFile: " + e);
                    }
                }
            }
            return null;
        } catch (FileNotFoundException e) {
            Rlog.e(LOG_TAG, "Carrier Provisioning Urls file not found");
        } catch (XmlPullParserException e) {
            Rlog.e(LOG_TAG, "Xml parser exception reading Carrier Provisioning Urls file: " + e);
        } catch (IOException e) {
            Rlog.e(LOG_TAG, "I/O exception reading Carrier Provisioning Urls file: " + e);
        }
        return null;
    }

    /**
     * Get the mobile provisioning url.
     */
    public String getMobileProvisioningUrl() {
        String url = getProvisioningUrlBaseFromFile();
        if (TextUtils.isEmpty(url)) {
            url = mContext.getResources().getString(R.string.mobile_provisioning_url);
            Rlog.d(LOG_TAG, "getMobileProvisioningUrl: url from resource =" + url);
        } else {
            Rlog.d(LOG_TAG, "getMobileProvisioningUrl: url from File =" + url);
        }
        // Populate the iccid, imei and phone number in the provisioning url.
        if (!TextUtils.isEmpty(url)) {
            String phoneNumber = getLine1Number();
            if (TextUtils.isEmpty(phoneNumber)) {
                phoneNumber = "0000000000";
            }
            url = String.format(url,
                    getIccSerialNumber() /* ICCID */,
                    getDeviceId() /* IMEI */,
                    phoneNumber /* Phone number */);
        }

        return url;
    }

    /**
     * Check if there are matching tethering (i.e DUN) for the carrier.
     * @return true if there is a matching DUN APN.
     */
    public boolean hasMatchedTetherApnSetting() {
        NetworkRegistrationInfo nrs = getServiceState().getNetworkRegistrationInfo(
                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
        if (nrs != null) {
            return getDataNetworkController().getDataProfileManager()
                    .isTetheringDataProfileExisting(nrs.getAccessNetworkTechnology());
        }
        return false;
    }

    /**
     * Report on whether data connectivity is allowed for internet.
     *
     * @return {@code true} if internet data is allowed to be established.
     */
    public boolean isDataAllowed() {
        return getDataNetworkController().isInternetDataAllowed(false/* ignoreExistingNetworks */);
    }

    /**
     * Action set from carrier signalling broadcast receivers to enable/disable metered apns.
     */
    public void carrierActionSetMeteredApnsEnabled(boolean enabled) {
        mCarrierActionAgent.carrierActionSetMeteredApnsEnabled(enabled);
    }

    /**
     * Action set from carrier signalling broadcast receivers to enable/disable radio
     */
    public void carrierActionSetRadioEnabled(boolean enabled) {
        mCarrierActionAgent.carrierActionSetRadioEnabled(enabled);
    }

    /**
     * Action set from carrier app to start/stop reporting default network condition.
     */
    public void carrierActionReportDefaultNetworkStatus(boolean report) {
        mCarrierActionAgent.carrierActionReportDefaultNetworkStatus(report);
    }

    /**
     * Action set from carrier signalling broadcast receivers to reset all carrier actions
     */
    public void carrierActionResetAll() {
        mCarrierActionAgent.carrierActionReset();
    }

    /**
     * Notify registrants of a new ringing Connection.
     * Subclasses of Phone probably want to replace this with a
     * version scoped to their packages
     */
    public void notifyNewRingingConnectionP(Connection cn) {
        Rlog.i(LOG_TAG, String.format(
                "notifyNewRingingConnection: phoneId=[%d], connection=[%s], registrants=[%s]",
                getPhoneId(), cn, getNewRingingConnectionRegistrantsAsString()));
        if (!mIsVoiceCapable)
            return;
        AsyncResult ar = new AsyncResult(null, cn, null);
        mNewRingingConnectionRegistrants.notifyRegistrants(ar);
    }

    /**
     * helper for notifyNewRingingConnectionP(Connection) to create a string for a log message.
     *
     * @return a list of objects in mNewRingingConnectionRegistrants as a String
     */
    private String getNewRingingConnectionRegistrantsAsString() {
        List<String> registrants = new ArrayList<>();
        for (int i = 0; i < mNewRingingConnectionRegistrants.size(); i++) {
            registrants.add(mNewRingingConnectionRegistrants.get(i).toString());
        }
        return String.join(", ", registrants);
    }

    /**
     * Notify registrants of a new unknown connection.
     */
    public void notifyUnknownConnectionP(Connection cn) {
        mUnknownConnectionRegistrants.notifyResult(cn);
    }

    /**
     * Notify registrants if phone is video capable.
     */
    public void notifyForVideoCapabilityChanged(boolean isVideoCallCapable) {
        // Cache the current video capability so that we don't lose the information.
        mIsVideoCapable = isVideoCallCapable;

        AsyncResult ar = new AsyncResult(null, isVideoCallCapable, null);
        mVideoCapabilityChangedRegistrants.notifyRegistrants(ar);
    }

    /**
     * Notify registrants of a RING event.
     */
    private void notifyIncomingRing() {
        if (!mIsVoiceCapable)
            return;
        AsyncResult ar = new AsyncResult(null, this, null);
        mIncomingRingRegistrants.notifyRegistrants(ar);
    }

    /**
     * Send the incoming call Ring notification if conditions are right.
     */
    private void sendIncomingCallRingNotification(int token) {
        if (mIsVoiceCapable && !mDoesRilSendMultipleCallRing &&
                (token == mCallRingContinueToken)) {
            Rlog.d(LOG_TAG, "Sending notifyIncomingRing");
            notifyIncomingRing();
            sendMessageDelayed(
                    obtainMessage(EVENT_CALL_RING_CONTINUE, token, 0), mCallRingDelay);
        } else {
            Rlog.d(LOG_TAG, "Ignoring ring notification request,"
                    + " mDoesRilSendMultipleCallRing=" + mDoesRilSendMultipleCallRing
                    + " token=" + token
                    + " mCallRingContinueToken=" + mCallRingContinueToken
                    + " mIsVoiceCapable=" + mIsVoiceCapable);
        }
    }

    /**
     * Enable or disable always reporting signal strength changes from radio.
     *
     * @param isEnable {@code true} for enabling; {@code false} for disabling.
     */
    public void setAlwaysReportSignalStrength(boolean isEnable) {
        if (mDeviceStateMonitor != null) {
            mDeviceStateMonitor.setAlwaysReportSignalStrength(isEnable);
        }
    }

    /**
     * TODO: Adding a function for each property is not good.
     * A fucntion of type getPhoneProp(propType) where propType is an
     * enum of GSM+CDMA+LTE props would be a better approach.
     *
     * Get "Restriction of menu options for manual PLMN selection" bit
     * status from EF_CSP data, this belongs to "Value Added Services Group".
     * @return true if this bit is set or EF_CSP data is unavailable,
     * false otherwise
     */
    @UnsupportedAppUsage
    public boolean isCspPlmnEnabled() {
        return false;
    }

    /**
     * Return an interface to retrieve the ISIM records for IMS, if available.
     * @return the interface to retrieve the ISIM records, or null if not supported
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public IsimRecords getIsimRecords() {
        Rlog.e(LOG_TAG, "getIsimRecords() is only supported on LTE devices");
        return null;
    }

    /**
     * Retrieves the MSISDN from the UICC. For GSM/UMTS phones, this is equivalent to
     * {@link #getLine1Number()}. For CDMA phones, {@link #getLine1Number()} returns
     * the MDN, so this method is provided to return the MSISDN on CDMA/LTE phones.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public String getMsisdn() {
        return null;
    }

    /**
     * Retrieves the EF_PNN from the UICC For GSM/UMTS phones.
     */
    public String getPlmn() {
        return null;
    }

    public void notifyCallForwardingIndicator() {
    }

    /**
     * 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
     */
    public void setVoiceMessageWaiting(int line, int countWaiting) {
        // This function should be overridden by class GsmCdmaPhone.
        Rlog.e(LOG_TAG, "Error! This function should never be executed, inactive Phone.");
    }

    /**
     * Gets the USIM service table from the UICC, if present and available.
     * @return an interface to the UsimServiceTable record, or null if not available
     */
    public UsimServiceTable getUsimServiceTable() {
        IccRecords r = mIccRecords.get();
        return (r != null) ? r.getUsimServiceTable() : null;
    }

    /**
     * Gets the Uicc card corresponding to this phone.
     * @return the UiccCard object corresponding to the phone ID.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public UiccCard getUiccCard() {
        return mUiccController.getUiccCard(mPhoneId);
    }

    /**
     * Gets the Uicc port corresponding to this phone.
     * @return the UiccPort object corresponding to the phone ID.
     */
    public UiccPort getUiccPort() {
        return mUiccController.getUiccPort(mPhoneId);
    }

    /**
     * Set IMS registration state
     */
    public void setImsRegistrationState(boolean registered) {
    }

    /**
     * Return an instance of a IMS phone
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public Phone getImsPhone() {
        return mImsPhone;
    }

    @VisibleForTesting
    public void setImsPhone(ImsPhone imsPhone) {
        mImsPhone = imsPhone;
    }

    /**
     * Returns Carrier specific information that will be used to encrypt the IMSI and IMPI.
     * @param keyType whether the key is being used for WLAN or ePDG.
     * @param fallback whether or not to fall back to the encryption key info stored in carrier
     *                 config
     * @return ImsiEncryptionInfo which includes the Key Type, the Public Key
     *        {@link java.security.PublicKey} and the Key Identifier.
     *        The keyIdentifier This is used by the server to help it locate the private key to
     *        decrypt the permanent identity.
     */
    public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType, boolean fallback) {
        return null;
    }

    /**
     * Sets the carrier information needed to encrypt the IMSI and IMPI.
     * @param imsiEncryptionInfo Carrier specific information that will be used to encrypt the
     *        IMSI and IMPI. This includes the Key type, the Public key
     *        {@link java.security.PublicKey} and the Key identifier.
     */
    public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo) {
        return;
    }

    /**
     * Deletes all the keys for a given Carrier from the device keystore.
     * @param carrierId : the carrier ID which needs to be matched in the delete query
     */
    public void deleteCarrierInfoForImsiEncryption(int carrierId) {
        return;
    }

    /**
     * Deletes all the keys for a given Carrier from the device keystore.
     * @param carrierId : the carrier ID which needs to be matched in the delete query
     * @param simOperator : MccMnc which needs to be matched in the delete query.
     */
    public void deleteCarrierInfoForImsiEncryption(int carrierId, String simOperator) {

    }

    public int getCarrierId() {
        return TelephonyManager.UNKNOWN_CARRIER_ID;
    }

    public String getCarrierName() {
        return null;
    }

    public int getMNOCarrierId() {
        return TelephonyManager.UNKNOWN_CARRIER_ID;
    }

    public int getSpecificCarrierId() {
        return TelephonyManager.UNKNOWN_CARRIER_ID;
    }

    public String getSpecificCarrierName() {
        return null;
    }

    public int getCarrierIdListVersion() {
        return TelephonyManager.UNKNOWN_CARRIER_ID_LIST_VERSION;
    }

    public int getEmergencyNumberDbVersion() {
        return TelephonyManager.INVALID_EMERGENCY_NUMBER_DB_VERSION;
    }

    public void resolveSubscriptionCarrierId(String simState) {
    }

    /**
     *  Resets the Carrier Keys in the database. This involves 2 steps:
     *  1. Delete the keys from the database.
     *  2. Send an intent to download new Certificates.
     */
    public void resetCarrierKeysForImsiEncryption() {
        return;
    }

    /**
     * Return if UT capability of ImsPhone is enabled or not
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public boolean isUtEnabled() {
        if (mImsPhone != null) {
            return mImsPhone.isUtEnabled();
        }
        return false;
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void dispose() {
    }

    /**
     * Dials a number.
     *
     * @param dialString The number to dial.
     * @param dialArgs Parameters to dial with.
     * @return The Connection.
     * @throws CallStateException
     */
    protected Connection dialInternal(String dialString, DialArgs dialArgs)
            throws CallStateException {
        // dialInternal shall be overriden by GsmCdmaPhone
        return null;
    }

    /*
     * This function is for CSFB SS. GsmCdmaPhone overrides this function.
     */
    public void setCallWaiting(boolean enable, int serviceClass, Message onComplete) {
    }

    public void queryCLIP(Message onComplete) {
    }

    /*
     * Returns the subscription id.
     */
    @UnsupportedAppUsage
    public int getSubId() {
        return mSubscriptionManagerService.getSubId(mPhoneId);
    }

    /**
     * Returns the phone id.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public int getPhoneId() {
        return mPhoneId;
    }

    /**
     * Override the service provider name and the operator name for the current ICCID.
     */
    public boolean setOperatorBrandOverride(String brand) {
        return false;
    }

    /**
     * Override the roaming indicator for the current ICCID.
     */
    public boolean setRoamingOverride(List<String> gsmRoamingList,
            List<String> gsmNonRoamingList, List<String> cdmaRoamingList,
            List<String> cdmaNonRoamingList) {
        String iccId = getIccSerialNumber();
        if (TextUtils.isEmpty(iccId)) {
            return false;
        }

        setRoamingOverrideHelper(gsmRoamingList, GSM_ROAMING_LIST_OVERRIDE_PREFIX, iccId);
        setRoamingOverrideHelper(gsmNonRoamingList, GSM_NON_ROAMING_LIST_OVERRIDE_PREFIX, iccId);
        setRoamingOverrideHelper(cdmaRoamingList, CDMA_ROAMING_LIST_OVERRIDE_PREFIX, iccId);
        setRoamingOverrideHelper(cdmaNonRoamingList, CDMA_NON_ROAMING_LIST_OVERRIDE_PREFIX, iccId);

        // Refresh.
        ServiceStateTracker tracker = getServiceStateTracker();
        if (tracker != null) {
            tracker.pollState();
        }
        return true;
    }

    private void setRoamingOverrideHelper(List<String> list, String prefix, String iccId) {
        SharedPreferences.Editor spEditor =
                PreferenceManager.getDefaultSharedPreferences(mContext).edit();
        String key = prefix + iccId;
        if (list == null || list.isEmpty()) {
            spEditor.remove(key).commit();
        } else {
            spEditor.putStringSet(key, new HashSet<String>(list)).commit();
        }
    }

    public boolean isMccMncMarkedAsRoaming(String mccMnc) {
        return getRoamingOverrideHelper(GSM_ROAMING_LIST_OVERRIDE_PREFIX, mccMnc);
    }

    public boolean isMccMncMarkedAsNonRoaming(String mccMnc) {
        return getRoamingOverrideHelper(GSM_NON_ROAMING_LIST_OVERRIDE_PREFIX, mccMnc);
    }

    public boolean isSidMarkedAsRoaming(int SID) {
        return getRoamingOverrideHelper(CDMA_ROAMING_LIST_OVERRIDE_PREFIX,
                Integer.toString(SID));
    }

    public boolean isSidMarkedAsNonRoaming(int SID) {
        return getRoamingOverrideHelper(CDMA_NON_ROAMING_LIST_OVERRIDE_PREFIX,
                Integer.toString(SID));
    }

    /**
     * Query the IMS Registration Status.
     *
     * @return true if IMS is Registered
     */
    public boolean isImsRegistered() {
        Phone imsPhone = mImsPhone;
        boolean isImsRegistered = false;
        if (imsPhone != null) {
            isImsRegistered = imsPhone.isImsRegistered();
        } else {
            ServiceStateTracker sst = getServiceStateTracker();
            if (sst != null) {
                isImsRegistered = sst.isImsRegistered();
            }
        }
        Rlog.d(LOG_TAG, "isImsRegistered =" + isImsRegistered);
        return isImsRegistered;
    }

    /**
     * Get Wifi Calling Feature Availability
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public boolean isWifiCallingEnabled() {
        Phone imsPhone = mImsPhone;
        boolean isWifiCallingEnabled = false;
        if (imsPhone != null) {
            isWifiCallingEnabled = imsPhone.isWifiCallingEnabled();
        }
        Rlog.d(LOG_TAG, "isWifiCallingEnabled =" + isWifiCallingEnabled);
        return isWifiCallingEnabled;
    }

    /**
     * @return true if the IMS capability for the registration technology specified is available,
     * false otherwise.
     */
    public boolean isImsCapabilityAvailable(int capability, int regTech) throws ImsException {
        Phone imsPhone = mImsPhone;
        boolean isAvailable = false;
        if (imsPhone != null) {
            isAvailable = imsPhone.isImsCapabilityAvailable(capability, regTech);
        }
        Rlog.d(LOG_TAG, "isImsCapabilityAvailable, capability=" + capability + ", regTech="
                + regTech + ", isAvailable=" + isAvailable);
        return isAvailable;
    }

    /**
     * Get Volte Feature Availability
     * @deprecated Use {@link #isVoiceOverCellularImsEnabled} instead.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    @Deprecated
    public boolean isVolteEnabled() {
        return isVoiceOverCellularImsEnabled();
    }

    /**
     * @return {@code true} if voice over IMS on cellular is enabled, {@code false} otherwise.
     */
    public boolean isVoiceOverCellularImsEnabled() {
        Phone imsPhone = mImsPhone;
        boolean isVolteEnabled = false;
        if (imsPhone != null) {
            isVolteEnabled = imsPhone.isVoiceOverCellularImsEnabled();
        }
        Rlog.d(LOG_TAG, "isVoiceOverCellularImsEnabled=" + isVolteEnabled);
        return isVolteEnabled;
    }

    /**
     * @return the IMS MmTel Registration technology for this Phone, defined in
     * {@link ImsRegistrationImplBase}.
     */
    public int getImsRegistrationTech() {
        Phone imsPhone = mImsPhone;
        int regTech = ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
        if (imsPhone != null) {
            regTech = imsPhone.getImsRegistrationTech();
        }
        Rlog.d(LOG_TAG, "getImsRegistrationTechnology =" + regTech);
        return regTech;
    }

    /**
     * Get the IMS MmTel Registration technology for this Phone, defined in
     * {@link ImsRegistrationImplBase}.
     */
    public void getImsRegistrationTech(Consumer<Integer> callback) {
        Phone imsPhone = mImsPhone;
        if (imsPhone != null) {
            imsPhone.getImsRegistrationTech(callback);
        } else {
            callback.accept(ImsRegistrationImplBase.REGISTRATION_TECH_NONE);
        }
    }

    /**
     * Asynchronously get the IMS MmTel Registration state for this Phone.
     */
    public void getImsRegistrationState(Consumer<Integer> callback) {
        Phone imsPhone = mImsPhone;
        if (imsPhone != null) {
            imsPhone.getImsRegistrationState(callback);
        } else {
            callback.accept(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
        }
    }


    private boolean getRoamingOverrideHelper(String prefix, String key) {
        String iccId = getIccSerialNumber();
        if (TextUtils.isEmpty(iccId) || TextUtils.isEmpty(key)) {
            return false;
        }

        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
        Set<String> value = sp.getStringSet(prefix + iccId, null);
        if (value == null) {
            return false;
        }
        return value.contains(key);
    }

    /**
     * @return returns the latest radio state from the modem
     */
    public int getRadioPowerState() {
        return mCi.getRadioState();
    }

    /**
     * Is Radio Present on the device and is it accessible
     */
    public boolean isRadioAvailable() {
        return mCi.getRadioState() != TelephonyManager.RADIO_POWER_UNAVAILABLE;
    }

    /**
     * Is Radio turned on
     */
    public boolean isRadioOn() {
        return mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON;
    }

    /**
     * shutdown Radio gracefully
     */
    public void shutdownRadio() {
        getServiceStateTracker().requestShutdown();
    }

    /**
     * Return true if the device is shutting down.
     */
    public boolean isShuttingDown() {
        return getServiceStateTracker().isDeviceShuttingDown();
    }

    /**
     *  Set phone radio capability
     *
     *  @param rc the phone radio capability defined in
     *         RadioCapability. It's a input object used to transfer parameter to logic modem
     *  @param response Callback message.
     */
    public void setRadioCapability(RadioCapability rc, Message response) {
        mCi.setRadioCapability(rc, response);
    }

    /**
     *  Get phone radio access family
     *
     *  @return a bit mask to identify the radio access family.
     */
    public int getRadioAccessFamily() {
        final RadioCapability rc = getRadioCapability();
        return (rc == null ? RadioAccessFamily.RAF_UNKNOWN : rc.getRadioAccessFamily());
    }

    /**
     *  Get the associated data modems Id.
     *
     *  @return a String containing the id of the data modem
     */
    public String getModemUuId() {
        final RadioCapability rc = getRadioCapability();
        return (rc == null ? "" : rc.getLogicalModemUuid());
    }

    /**
     *  Get phone radio capability
     *
     *  @return the capability of the radio defined in RadioCapability
     */
    public RadioCapability getRadioCapability() {
        return mRadioCapability.get();
    }

    /**
     *  The RadioCapability has changed. This comes up from the RIL and is called when radios first
     *  become available or after a capability switch.  The flow is we use setRadioCapability to
     *  request a change with the RIL and get an UNSOL response with the new data which gets set
     *  here.
     *
     *  @param rc the phone radio capability currently in effect for this phone.
     *  @param capabilitySwitched whether this method called after a radio capability switch
     *      completion or called when radios first become available.
     */
    public void radioCapabilityUpdated(RadioCapability rc, boolean capabilitySwitched) {
        // Called when radios first become available or after a capability switch
        // Update the cached value
        mRadioCapability.set(rc);

        if (SubscriptionManager.isValidSubscriptionId(getSubId())) {
            boolean restoreSelection = !mContext.getResources().getBoolean(
                    com.android.internal.R.bool.skip_restoring_network_selection);
            sendSubscriptionSettings(restoreSelection);
        }

        // When radio capability switch is done, query IMEI value and update it in Phone objects
        // to make it in sync with the IMEI value currently used by Logical-Modem.
        if (capabilitySwitched) {
            mCi.getImei(obtainMessage(EVENT_GET_DEVICE_IMEI_DONE));
            mCi.getDeviceIdentity(obtainMessage(EVENT_GET_DEVICE_IDENTITY_DONE));
        }
    }

    public void sendSubscriptionSettings(boolean restoreNetworkSelection) {
        // Send settings down
        if (mIsAllowedNetworkTypesLoadedFromDb) {
            updateAllowedNetworkTypes(null);
        }

        if (restoreNetworkSelection) {
            restoreSavedNetworkSelection(null);
        }

        updateUsageSetting();
    }

    private int getResolvedUsageSetting(int subId) {
        SubscriptionInfo subInfo = null;
        SubscriptionInfoInternal subInfoInternal = mSubscriptionManagerService
                .getSubscriptionInfoInternal(subId);
        if (subInfoInternal != null) {
            subInfo = subInfoInternal.toSubscriptionInfo();
        }

        if (subInfo == null) {
            loge("Failed to get SubscriptionInfo for subId=" + subId);
            return SubscriptionManager.USAGE_SETTING_UNKNOWN;
        }

        if (subInfo.getUsageSetting() == SubscriptionManager.USAGE_SETTING_UNKNOWN) {
            return SubscriptionManager.USAGE_SETTING_UNKNOWN;
        }

        if (subInfo.getUsageSetting() != SubscriptionManager.USAGE_SETTING_DEFAULT) {
            return subInfo.getUsageSetting();
        }

        if (subInfo.isOpportunistic()) {
            return SubscriptionManager.USAGE_SETTING_DATA_CENTRIC;
        } else {
            return mContext.getResources().getInteger(
                    com.android.internal.R.integer.config_default_cellular_usage_setting);
        }
    }

    /**
     * Attempt to update the usage setting.
     *
     * @return whether the usage setting will be updated (used for test)
     */
    public boolean updateUsageSetting() {
        if (!mIsUsageSettingSupported) return false;

        final int subId = getSubId();
        if (!SubscriptionManager.isValidSubscriptionId(subId)) return false;

        final int lastPreferredUsageSetting = mPreferredUsageSetting;

        mPreferredUsageSetting = getResolvedUsageSetting(subId);
        if (mPreferredUsageSetting == SubscriptionManager.USAGE_SETTING_UNKNOWN) {
            loge("Usage Setting is Supported but Preferred Setting Unknown!");
            return false;
        }

        // We might get a lot of requests to update, so definitely we don't want to hammer
        // the modem with multiple duplicate requests for usage setting updates
        if (mPreferredUsageSetting == lastPreferredUsageSetting) return false;

        String logStr = "mPreferredUsageSetting=" + mPreferredUsageSetting
                + ", lastPreferredUsageSetting=" + lastPreferredUsageSetting
                + ", mUsageSettingFromModem=" + mUsageSettingFromModem;
        logd(logStr);
        mLocalLog.log(logStr);

        // If the modem value hasn't been updated, request it.
        if (mUsageSettingFromModem == SubscriptionManager.USAGE_SETTING_UNKNOWN) {
            mCi.getUsageSetting(obtainMessage(EVENT_GET_USAGE_SETTING_DONE));
            // If the modem value is already known, and the value has changed, proceed to update.
        } else if (mPreferredUsageSetting != mUsageSettingFromModem) {
            mCi.setUsageSetting(obtainMessage(EVENT_SET_USAGE_SETTING_DONE),
                    mPreferredUsageSetting);
        }
        return true;
    }



    /**
     * Registers the handler when phone radio  capability is changed.
     *
     * @param h Handler for notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForRadioCapabilityChanged(Handler h, int what, Object obj) {
        mCi.registerForRadioCapabilityChanged(h, what, obj);
    }

    /**
     * Unregister for notifications when phone radio type and access technology is changed.
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForRadioCapabilityChanged(Handler h) {
        mCi.unregisterForRadioCapabilityChanged(this);
    }

    /**
     * Determines if the connection to IMS services are available yet.
     * @return {@code true} if the connection to IMS services are available.
     */
    public boolean isImsAvailable() {
        if (mImsPhone == null) {
            return false;
        }

        return mImsPhone.isImsAvailable();
    }

    /**
     * Determines if video calling is enabled for the phone.
     *
     * @return {@code true} if video calling is enabled, {@code false} otherwise.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public boolean isVideoEnabled() {
        Phone imsPhone = mImsPhone;
        if (imsPhone != null) {
            return imsPhone.isVideoEnabled();
        }
        return false;
    }

    /**
     * Returns the modem activity information
     */
    public void getModemActivityInfo(Message response, WorkSource workSource)  {
        mCi.getModemActivityInfo(response, workSource);
    }

    /**
     * Control the data throttling at modem.
     *
     * @param result Message that will be sent back to the requester
     * @param workSource calling Worksource
     * @param dataThrottlingAction the DataThrottlingAction that is being requested. Defined in
     *      android.telephony.TelephonyManger.
     * @param completionWindowMillis milliseconds in which data throttling action has to be
     *      achieved.
     */
    public void setDataThrottling(Message result, WorkSource workSource,
            int dataThrottlingAction, long completionWindowMillis) {
        mCi.setDataThrottling(result, workSource, dataThrottlingAction, completionWindowMillis);
    }

    /**
     * Set allowed carriers
     */
    public void setAllowedCarriers(CarrierRestrictionRules carrierRestrictionRules,
            Message response, WorkSource workSource) {
        mCi.setAllowedCarriers(carrierRestrictionRules, response, workSource);
    }

    /** Sets the SignalStrength reporting criteria. */
    public void setLinkCapacityReportingCriteria(int[] dlThresholds, int[] ulThresholds, int ran) {
        // no-op default implementation
    }

    /**
     * Get allowed carriers
     */
    public void getAllowedCarriers(Message response, WorkSource workSource) {
        mCi.getAllowedCarriers(response, workSource);
    }

    /**
     * Returns the locale based on the carrier properties (such as {@code ro.carrier}) and
     * SIM preferences.
     */
    public Locale getLocaleFromSimAndCarrierPrefs() {
        final IccRecords records = mIccRecords.get();
        if (records != null && records.getSimLanguage() != null) {
            return new Locale(records.getSimLanguage());
        }

        return getLocaleFromCarrierProperties();
    }

    public boolean updateCurrentCarrierInProvider() {
        return false;
    }

    @UnsupportedAppUsage
    public IccSmsInterfaceManager getIccSmsInterfaceManager(){
        return null;
    }

    protected boolean isMatchGid(String gid) {
        String gid1 = getGroupIdLevel1();
        int gidLength = gid.length();
        if (!TextUtils.isEmpty(gid1) && (gid1.length() >= gidLength)
                && gid1.substring(0, gidLength).equalsIgnoreCase(gid)) {
            return true;
        }
        return false;
    }

    public static void checkWfcWifiOnlyModeBeforeDial(Phone imsPhone, int phoneId, Context context)
            throws CallStateException {
        if (imsPhone == null || !imsPhone.isWifiCallingEnabled()) {
            ImsManager imsManager = ImsManager.getInstance(context, phoneId);
            boolean wfcWiFiOnly = (imsManager.isWfcEnabledByPlatform()
                    && imsManager.isWfcEnabledByUser() && (imsManager.getWfcMode()
                    == ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY));
            if (wfcWiFiOnly) {
                throw new CallStateException(
                        CallStateException.ERROR_OUT_OF_SERVICE,
                        "WFC Wi-Fi Only Mode: IMS not registered");
            }
        }
    }

    public boolean isImsServiceSimultaneousCallingSupportCapable(Context context) {
        if (!mFeatureFlags.simultaneousCallingIndications()) return false;
        boolean capable = false;
        ImsManager imsManager = ImsManager.getInstance(context, mPhoneId);
        if (imsManager != null) {
            try {
                capable = imsManager.isCapable(CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING);
            } catch (ImsException e) {
                loge("initializeTerminalBasedCallWaiting : exception " + e);
            }
        }
        return capable;
    }

    public void startRingbackTone() {
    }

    public void stopRingbackTone() {
    }

    public void callEndCleanupHandOverCallIfAny() {
    }

    /**
     * Cancel USSD session.
     *
     * @param msg The message to dispatch when the USSD session terminated.
     */
    public void cancelUSSD(Message msg) {
    }

    /**
     * Set boolean broadcastEmergencyCallStateChanges
     */
    public abstract void setBroadcastEmergencyCallStateChanges(boolean broadcast);

    public abstract void sendEmergencyCallStateChange(boolean callActive);

    /**
     * This function returns the parent phone of the current phone. It is applicable
     * only for IMS phone (function is overridden by ImsPhone). For others the phone
     * object itself is returned.
     * @return
     */
    public Phone getDefaultPhone() {
        return this;
    }

    /**
     * SIP URIs aliased to the current subscriber given by the IMS implementation.
     * Applicable only on IMS; used in absence of line1number.
     * @return array of SIP URIs aliased to the current subscriber
     */
    public Uri[] getCurrentSubscriberUris() {
        return null;
    }

    public AppSmsManager getAppSmsManager() {
        return mAppSmsManager;
    }

    /**
     * Set SIM card power state.
     * @param state State of SIM (power down, power up, pass through)
     * - {@link android.telephony.TelephonyManager#CARD_POWER_DOWN}
     * - {@link android.telephony.TelephonyManager#CARD_POWER_UP}
     * - {@link android.telephony.TelephonyManager#CARD_POWER_UP_PASS_THROUGH}
     **/
    public void setSimPowerState(int state, Message result, WorkSource workSource) {
        mCi.setSimCardPower(state, result, workSource);
    }

    /**
     * Enable or disable Voice over NR (VoNR)
     * @param enabled enable or disable VoNR.
     **/
    public void setVoNrEnabled(boolean enabled, Message result, WorkSource workSource) {
        mCi.setVoNrEnabled(enabled, result, workSource);
    }

    /**
     * Is voice over NR enabled
     */
    public void isVoNrEnabled(Message message, WorkSource workSource) {
        mCi.isVoNrEnabled(message, workSource);
    }

    public void setCarrierTestOverride(String mccmnc, String imsi, String iccid, String gid1,
            String gid2, String pnn, String spn, String carrierPrivilegeRules, String apn) {
    }

    /**
     * Check if the device can only make the emergency call. The device is emergency call only if
     * none of the phone is in service, and one of them has the capability to make the emergency
     * call.
     *
     * @return {@code True} if the device is emergency call only, otherwise return {@code False}.
     */
    public static boolean isEmergencyCallOnly() {
        boolean isEmergencyCallOnly = false;
        for (Phone phone : PhoneFactory.getPhones()) {
            if (phone != null) {
                ServiceStateTracker sst = phone.getServiceStateTracker();
                ServiceState ss = sst.getServiceState();
                // Combined reg state is in service, hence the device is not emergency call only.
                if (sst.getCombinedRegState(ss) == ServiceState.STATE_IN_SERVICE) {
                    return false;
                }
                isEmergencyCallOnly |= ss.isEmergencyOnly();
            }
        }
        return isEmergencyCallOnly;
    }

    // Return true if either CSIM or RUIM app is present. By default it returns false.
    public boolean isCdmaSubscriptionAppPresent() {
        return false;
    }

    /**
     * Enable or disable uicc applications.
     * @param enable whether to enable or disable uicc applications.
     * @param onCompleteMessage callback for async operation. Ignored if blockingCall is true.
     */
    public void enableUiccApplications(boolean enable, Message onCompleteMessage) {}

    /**
     * Whether disabling a physical subscription is supported or not.
     */
    public boolean canDisablePhysicalSubscription() {
        return false;
    }

    /**
     * Get the HAL version.
     *
     * @return the current HalVersion
     *
     * @deprecated Use {@link #getHalVersion(int service)} instead.
     */
    @Deprecated
    public HalVersion getHalVersion() {
        return getHalVersion(HAL_SERVICE_RADIO);
    }

    /**
     * Get the HAL version with a specific service.
     *
     * @param service the service id to query
     * @return the current HalVersion for a specific service
     *
     */
    public HalVersion getHalVersion(@HalService int service) {
        if (mCi != null && mCi instanceof RIL) {
            return ((RIL) mCi).getHalVersion(service);
        }
        return RIL.RADIO_HAL_VERSION_UNKNOWN;
    }

    /**
     * Get the SIM's MCC/MNC
     *
     * @return MCC/MNC in string format, empty string if not available.
     */
    @NonNull
    public String getOperatorNumeric() {
        return "";
    }

    /** Returns the {@link VoiceCallSessionStats} for this phone ID. */
    public VoiceCallSessionStats getVoiceCallSessionStats() {
        return mVoiceCallSessionStats;
    }

    /** Sets the {@link VoiceCallSessionStats} mock for this phone ID during unit testing. */
    @VisibleForTesting
    public void setVoiceCallSessionStats(VoiceCallSessionStats voiceCallSessionStats) {
        mVoiceCallSessionStats = voiceCallSessionStats;
    }

    /** Returns the {@link SmsStats} for this phone ID. */
    public SmsStats getSmsStats() {
        return mSmsStats;
    }

    /** Sets the {@link SmsStats} mock for this phone ID during unit testing. */
    @VisibleForTesting
    public void setSmsStats(SmsStats smsStats) {
        mSmsStats = smsStats;
    }

    /** Getter for Telephony Analytics */
    public TelephonyAnalytics getTelephonyAnalytics() {
        return mTelephonyAnalytics;
    }

    /** @hide */
    public CarrierPrivilegesTracker getCarrierPrivilegesTracker() {
        return null;
    }

    public boolean useSsOverIms(Message onComplete) {
        return false;
    }

    /**
     * Check if device is idle. Device is idle when it is not in high power consumption mode.
     *
     * @see DeviceStateMonitor#shouldEnableHighPowerConsumptionIndications()
     *
     * @return true if device is idle
     */
    public boolean isDeviceIdle() {
        DeviceStateMonitor dsm = getDeviceStateMonitor();
        if (dsm == null) {
            Rlog.e(LOG_TAG, "isDeviceIdle: DeviceStateMonitor is null");
            return false;
        }
        return !dsm.shouldEnableHighPowerConsumptionIndications();
    }

    /**
     * Get notified when device idleness state has changed
     *
     * @param isIdle true if the new state is idle
     */
    public void notifyDeviceIdleStateChanged(boolean isIdle) {
        SignalStrengthController ssc = getSignalStrengthController();
        if (ssc == null) {
            Rlog.e(LOG_TAG, "notifyDeviceIdleStateChanged: SignalStrengthController is null");
            return;
        }
        ssc.onDeviceIdleStateChanged(isIdle);
    }

    /**
     * Returns a list of the equivalent home PLMNs (EF_EHPLMN) from the USIM app.
     *
     * @return A list of equivalent home PLMNs. Returns an empty list if EF_EHPLMN is empty or
     * does not exist on the SIM card.
     */
    public @NonNull List<String> getEquivalentHomePlmns() {
        return Collections.emptyList();
    }

    /**
     *
     * @return
     */
    public @NonNull List<String> getDataServicePackages() {
        return Collections.emptyList();
    }

    /**
     * Return link bandwidth estimator
     */
    public LinkBandwidthEstimator getLinkBandwidthEstimator() {
        return mLinkBandwidthEstimator;
    }

    /**
     * Request to get the current slicing configuration including URSP rules and
     * NSSAIs (configured, allowed and rejected).
     */
    public void getSlicingConfig(Message response) {
        mCi.getSlicingConfig(response);
    }

    /**
     * Returns the InboundSmsHandler object for this phone
     */
    public InboundSmsHandler getInboundSmsHandler(boolean is3gpp2) {
        return null;
    }

    /**
     * @return The data network controller
     */
    public @Nullable DataNetworkController getDataNetworkController() {
        return mDataNetworkController;
    }

    /**
     * @return The data settings manager
     */
    public @Nullable DataSettingsManager getDataSettingsManager() {
        if (mDataNetworkController == null) return null;
        return mDataNetworkController.getDataSettingsManager();
    }

    /**
     * Used in unit tests to set whether the AllowedNetworkTypes is loaded from Db.  Should not
     * be used otherwise.
     *
     * @return {@code true} if the AllowedNetworkTypes is loaded from Db,
     * {@code false} otherwise.
     */
    @VisibleForTesting
    public boolean isAllowedNetworkTypesLoadedFromDb() {
        return mIsAllowedNetworkTypesLoadedFromDb;
    }

    /**
     * Returns the user's last setting for terminal-based call waiting
     * @param forCsOnly indicates the caller expects the result for CS calls only
     */
    public int getTerminalBasedCallWaitingState(boolean forCsOnly) {
        return CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED;
    }

    /**
     * Notifies the change of the user setting of the terminal-based call waiting service
     * to IMS service.
     */
    public void setTerminalBasedCallWaitingStatus(int state) {
    }

    /**
     * Notifies that the IMS service connected supports the terminal-based call waiting service
     */
    public void setTerminalBasedCallWaitingSupported(boolean supported) {
    }

    /**
     * Notifies the NAS and RRC layers of the radio the type of upcoming IMS traffic.
     *
     * @param token A nonce to identify the request.
     * @param trafficType IMS traffic type like registration, voice, video, SMS, emergency, and etc.
     * @param accessNetworkType The type of the radio access network used.
     * @param trafficDirection Indicates whether traffic is originated by mobile originated or
     *        mobile terminated use case eg. MO/MT call/SMS etc.
     * @param response is callback message.
     */
    public void startImsTraffic(int token,
            @MmTelFeature.ImsTrafficType int trafficType,
            @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType,
            @MmTelFeature.ImsTrafficDirection int trafficDirection, Message response) {
        mCi.startImsTraffic(token, trafficType, accessNetworkType, trafficDirection, response);
    }

    /**
     * Notifies IMS traffic has been stopped.
     *
     * @param token The token assigned by startImsTraffic.
     * @param response is callback message.
     */
    public void stopImsTraffic(int token, Message response) {
        mCi.stopImsTraffic(token, response);
    }

    /**
     * Register for notifications of connection setup failure
     *
     * @param h Handler for notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForConnectionSetupFailure(Handler h, int what, Object obj) {
        mCi.registerForConnectionSetupFailure(h, what, obj);
    }

    /**
     * Unregister for notifications of connection setup failure
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForConnectionSetupFailure(Handler h) {
        mCi.unregisterForConnectionSetupFailure(h);
    }

    /**
     * Triggers the UE initiated EPS fallback procedure.
     *
     * @param reason specifies the reason for EPS fallback.
     * @param response is callback message.
     */
    public void triggerEpsFallback(@MmTelFeature.EpsFallbackReason int reason, Message response) {
        mCi.triggerEpsFallback(reason, response);
    }

    /**
     * Notifies the recommended bit rate for the indicated logical channel and direction.
     *
     * @param mediaType MediaType is used to identify media stream such as audio or video.
     * @param direction Direction of this packet stream (e.g. uplink or downlink).
     * @param bitsPerSecond The recommended bit rate for the UE for a specific logical channel and
     *        a specific direction by NW.
     */
    public void triggerNotifyAnbr(int mediaType, int direction, int bitsPerSecond) {
    }

    /**
     * Sets the emergency mode
     *
     * @param emcMode The radio emergency mode type.
     * @param result Callback message.
     */
    public void setEmergencyMode(@EmergencyConstants.EmergencyMode int emcMode,
            @Nullable Message result) {
        mCi.setEmergencyMode(emcMode, result);
    }

    /**
     * Triggers an emergency network scan.
     *
     * @param accessNetwork Contains the list of access network types to be prioritized
     *        during emergency scan. The 1st entry has the highest priority.
     * @param scanType Indicates the type of scans to be performed i.e. limited scan,
     *        full service scan or any scan.
     * @param result Callback message.
     */
    public void triggerEmergencyNetworkScan(
            @NonNull @AccessNetworkConstants.RadioAccessNetworkType int[] accessNetwork,
            @DomainSelectionService.EmergencyScanType int scanType, @Nullable Message result) {
        mCi.triggerEmergencyNetworkScan(accessNetwork, scanType, result);
    }

    /**
     * Cancels ongoing emergency network scan
     * @param resetScan Indicates how the next {@link #triggerEmergencyNetworkScan} should work.
     *        If {@code true}, then the modem shall start the new scan from the beginning,
     *        otherwise the modem shall resume from the last search.
     * @param result Callback message.
     */
    public void cancelEmergencyNetworkScan(boolean resetScan, @Nullable Message result) {
        mCi.cancelEmergencyNetworkScan(resetScan, result);
    }

    /**
     * Exits ongoing emergency mode
     * @param result Callback message.
     */
    public void exitEmergencyMode(@Nullable Message result) {
        mCi.exitEmergencyMode(result);
    }

    /**
     * Registers for emergency network scan result.
     *
     * @param h Handler for notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForEmergencyNetworkScan(@NonNull Handler h,
            int what, @Nullable Object obj) {
        mCi.registerForEmergencyNetworkScan(h, what, obj);
    }

    /**
     * Unregisters for emergency network scan result.
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForEmergencyNetworkScan(@NonNull Handler h) {
        mCi.unregisterForEmergencyNetworkScan(h);
    }

    /**
     * Notifies that IMS deregistration is triggered.
     *
     * @param reason the reason why the deregistration is triggered.
     */
    public void triggerImsDeregistration(
            @ImsRegistrationImplBase.ImsDeregistrationReason int reason) {
        if (mImsPhone != null) {
            mImsPhone.triggerImsDeregistration(reason);
        }
    }

    /**
     * Registers for the domain selected for emergency calls.
     *
     * @param h Handler for notification message.
     * @param what User-defined message code.
     * @param obj User object.
     */
    public void registerForEmergencyDomainSelected(
            @NonNull Handler h, int what, @Nullable Object obj) {
    }

    /**
     * Unregisters for the domain selected for emergency calls.
     *
     * @param h Handler to be removed from the registrant list.
     */
    public void unregisterForEmergencyDomainSelected(@NonNull Handler h) {
    }

    /**
     * Notifies the domain selected.
     *
     * @param transportType The preferred transport type.
     */
    public void notifyEmergencyDomainSelected(
            @AccessNetworkConstants.TransportType int transportType) {
    }

    /**
     * @return Telephony tester instance.
     */
    public @Nullable TelephonyTester getTelephonyTester() {
        return mTelephonyTester;
    }

    /**
     * @return User handle associated with the phone's subscription id. {@code null} if subscription
     * is invalid or not found.
     */
    @Nullable
    public UserHandle getUserHandle() {
        int subId = getSubId();

        UserHandle userHandle = null;
        try {
            SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
            if (subManager != null) {
                userHandle = subManager.getSubscriptionUserHandle(subId);
            }
        } catch (IllegalArgumentException ex) {
            loge("getUserHandle: ex=" + ex);
        }

        return userHandle;
    }

    /**
     * Checks if the context user is a managed profile.
     *
     * Note that this applies specifically to <em>managed</em> profiles.
     *
     * @return whether the context user is a managed profile.
     */
    public boolean isManagedProfile() {
        UserHandle userHandle = getUserHandle();
        UserManager userManager = mContext.getSystemService(UserManager.class);
        if (userHandle == null || userManager == null) return false;
        return userManager.isManagedProfile(userHandle.getIdentifier());
    }

    /**
     * @return global null cipher and integrity enabled preference
     */
    public boolean getNullCipherAndIntegrityEnabledPreference() {
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
        return sp.getBoolean(PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED, true);
    }

    /**
     * @return whether or not this Phone interacts with a modem that supports the null cipher
     * and integrity feature.
     */
    public boolean isNullCipherAndIntegritySupported() {
        return false;
    }

    /**
     * Override to implement handling of an update to the enablement of the null cipher and
     * integrity preference.
     * {@see #PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED}
     */
    public void handleNullCipherEnabledChange() {
    }

    /**
     * @return whether or not this Phone interacts with a modem that supports the cellular
     * identifier disclosure transparency feature.
     */
    public boolean isIdentifierDisclosureTransparencySupported() {
        return false;
    }

    /**
     * @return global cellular identifier disclosure transparency enabled preference
     */
    public boolean getIdentifierDisclosureNotificationsPreferenceEnabled() {
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
        return sp.getBoolean(PREF_IDENTIFIER_DISCLOSURE_NOTIFICATIONS_ENABLED, false);
    }

    /**
     * Override to handle an update to the cellular identifier disclosure transparency preference.
     */
    public void handleIdentifierDisclosureNotificationPreferenceChange() {
    }

    /**
     * @return whether this Phone interacts with a modem that supports the null cipher
     * notification feature.
     */
    public boolean isNullCipherNotificationSupported() {
        return false;
    }

    /**
     * @return whether the global null cipher notifications preference is enabled.
     */
    public boolean getNullCipherNotificationsPreferenceEnabled() {
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
        return sp.getBoolean(PREF_NULL_CIPHER_NOTIFICATIONS_ENABLED, false);
    }

    /**
     * Override to handle an update to the null cipher notification preference.
     */
    public void handleNullCipherNotificationPreferenceChanged() {
    }

    /**
     * Refresh the safety sources in response to the identified broadcast.
     */
    public void refreshSafetySources(String refreshBroadcastId) {
    }

    /**
     * Notifies the IMS call status to the modem.
     *
     * @param imsCallInfo The list of {@link ImsCallInfo}.
     * @param response A callback to receive the response.
     */
    public void updateImsCallStatus(@NonNull List<ImsCallInfo> imsCallInfo, Message response) {
        mCi.updateImsCallStatus(imsCallInfo, response);
    }

    /**
     * Enables or disables N1 mode (access to 5G core network) in accordance with
     * 3GPP TS 24.501 4.9.
     * @param enable {@code true} to enable N1 mode, {@code false} to disable N1 mode.
     * @param result Callback message to receive the result.
     */
    public void setN1ModeEnabled(boolean enable, Message result) {
        mCi.setN1ModeEnabled(enable, result);
    }

    /**
     * Check whether N1 mode (access to 5G core network) is enabled or not.
     * @param result Callback message to receive the result.
     */
    public void isN1ModeEnabled(Message result) {
        mCi.isN1ModeEnabled(result);
    }

    /**
     * Return current cell broadcast ranges.
     */
    public List<CellBroadcastIdRange> getCellBroadcastIdRanges() {
        return new ArrayList<>();
    }

    /**
     * Set reception of cell broadcast messages with the list of the given ranges.
     */
    public void setCellBroadcastIdRanges(
            @NonNull List<CellBroadcastIdRange> ranges, Consumer<Integer> callback) {
        callback.accept(TelephonyManager.CELL_BROADCAST_RESULT_UNSUPPORTED);
    }

    /**
     * Start callback mode
     * @param type for callback mode entry.
     */
    public void startCallbackMode(@TelephonyManager.EmergencyCallbackModeType int type) {
        Rlog.d(LOG_TAG, "startCallbackMode:type=" + type);
        mNotifier.notifyCallbackModeStarted(this, type);
    }

    /**
     * Stop callback mode
     * @param type for callback mode exit.
     * @param reason for stopping callback mode.
     */
    public void stopCallbackMode(@TelephonyManager.EmergencyCallbackModeType int type,
            @TelephonyManager.EmergencyCallbackModeStopReason int reason) {
        Rlog.d(LOG_TAG, "stopCallbackMode:type=" + type + ", reason=" + reason);
        mNotifier.notifyCallbackModeStopped(this, type, reason);
    }

    /**
     * Notify carrier roaming non-terrestrial network mode changed
     * @param active {@code true} If the device is connected to carrier roaming
     *                           non-terrestrial network or was connected within the
     *                           {CarrierConfigManager#KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT}
     *                           duration, {code false} otherwise.
     */
    public void notifyCarrierRoamingNtnModeChanged(boolean active) {
        logd("notifyCarrierRoamingNtnModeChanged active:" + active);
        mNotifier.notifyCarrierRoamingNtnModeChanged(this, active);
    }

    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("Phone: subId=" + getSubId());
        pw.println(" mPhoneId=" + mPhoneId);
        pw.println(" mCi=" + mCi);
        pw.println(" mDnsCheckDisabled=" + mDnsCheckDisabled);
        pw.println(" mDoesRilSendMultipleCallRing=" + mDoesRilSendMultipleCallRing);
        pw.println(" mCallRingContinueToken=" + mCallRingContinueToken);
        pw.println(" mCallRingDelay=" + mCallRingDelay);
        pw.println(" mIsVoiceCapable=" + mIsVoiceCapable);
        pw.println(" mIccRecords=" + mIccRecords.get());
        pw.println(" mUiccApplication=" + mUiccApplication.get());
        pw.println(" mSmsStorageMonitor=" + mSmsStorageMonitor);
        pw.println(" mSmsUsageMonitor=" + mSmsUsageMonitor);
        pw.flush();
        pw.println(" mLooper=" + mLooper);
        pw.println(" mContext=" + mContext);
        pw.println(" mNotifier=" + mNotifier);
        pw.println(" mSimulatedRadioControl=" + mSimulatedRadioControl);
        pw.println(" mUnitTestMode=" + mUnitTestMode);
        pw.println(" isDnsCheckDisabled()=" + isDnsCheckDisabled());
        pw.println(" getUnitTestMode()=" + getUnitTestMode());
        pw.println(" getState()=" + getState());
        pw.println(" getIccSerialNumber()=" + Rlog.pii(LOG_TAG, getIccSerialNumber()));
        pw.println(" getIccRecordsLoaded()=" + getIccRecordsLoaded());
        pw.println(" getMessageWaitingIndicator()=" + getMessageWaitingIndicator());
        pw.println(" getCallForwardingIndicator()=" + getCallForwardingIndicator());
        pw.println(" isInEmergencyCall()=" + isInEmergencyCall());
        pw.flush();
        pw.println(" isInEcm()=" + isInEcm());
        pw.println(" getPhoneName()=" + getPhoneName());
        pw.println(" getPhoneType()=" + getPhoneType());
        pw.println(" getVoiceMessageCount()=" + getVoiceMessageCount());
        pw.println(" needsOtaServiceProvisioning=" + needsOtaServiceProvisioning());
        pw.println(" isInEmergencySmsMode=" + isInEmergencySmsMode());
        pw.println(" isEcmCanceledForEmergency=" + isEcmCanceledForEmergency());
        pw.println(" service state=" + getServiceState());
        pw.flush();
        pw.println("++++++++++++++++++++++++++++++++");

        if (mImsPhone != null) {
            try {
                mImsPhone.dump(fd, pw, args);
            } catch (Exception e) {
                e.printStackTrace();
            }

            pw.flush();
            pw.println("++++++++++++++++++++++++++++++++");
        }

        if (mDataNetworkController != null) {
            try {
                mDataNetworkController.dump(fd, pw, args);
            } catch (Exception e) {
                e.printStackTrace();
            }
            pw.flush();
            pw.println("++++++++++++++++++++++++++++++++");
        }

        if (getServiceStateTracker() != null) {
            try {
                getServiceStateTracker().dump(fd, pw, args);
            } catch (Exception e) {
                e.printStackTrace();
            }

            pw.flush();
            pw.println("++++++++++++++++++++++++++++++++");
        }

        if (getEmergencyNumberTracker() != null) {
            try {
                getEmergencyNumberTracker().dump(fd, pw, args);
            } catch (Exception e) {
                e.printStackTrace();
            }

            pw.flush();
            pw.println("++++++++++++++++++++++++++++++++");
        }

        if (getDisplayInfoController() != null) {
            try {
                getDisplayInfoController().dump(fd, pw, args);
            } catch (Exception e) {
                e.printStackTrace();
            }

            pw.flush();
            pw.println("++++++++++++++++++++++++++++++++");
        }

        if (mCarrierResolver != null) {
            try {
                mCarrierResolver.dump(fd, pw, args);
            } catch (Exception e) {
                e.printStackTrace();
            }

            pw.flush();
            pw.println("++++++++++++++++++++++++++++++++");
        }

        if (mCarrierActionAgent != null) {
            try {
                mCarrierActionAgent.dump(fd, pw, args);
            } catch (Exception e) {
                e.printStackTrace();
            }

            pw.flush();
            pw.println("++++++++++++++++++++++++++++++++");
        }

        if (mCarrierSignalAgent != null) {
            try {
                mCarrierSignalAgent.dump(fd, pw, args);
            } catch (Exception e) {
                e.printStackTrace();
            }

            pw.flush();
            pw.println("++++++++++++++++++++++++++++++++");
        }

        if (getCallTracker() != null) {
            try {
                getCallTracker().dump(fd, pw, args);
            } catch (Exception e) {
                e.printStackTrace();
            }

            pw.flush();
            pw.println("++++++++++++++++++++++++++++++++");
        }

        if (mSimActivationTracker != null) {
            try {
                mSimActivationTracker.dump(fd, pw, args);
            } catch (Exception e) {
                e.printStackTrace();
            }

            pw.flush();
            pw.println("++++++++++++++++++++++++++++++++");
        }

        if (mDeviceStateMonitor != null) {
            pw.println("DeviceStateMonitor:");
            mDeviceStateMonitor.dump(fd, pw, args);
            pw.println("++++++++++++++++++++++++++++++++");
        }

        if (mSignalStrengthController != null) {
            pw.println("SignalStrengthController:");
            mSignalStrengthController.dump(fd, pw, args);
            pw.println("++++++++++++++++++++++++++++++++");
        }

        if (mAccessNetworksManager != null) {
            mAccessNetworksManager.dump(fd, pw, args);
        }

        if (mCi != null && mCi instanceof RIL) {
            try {
                ((RIL)mCi).dump(fd, pw, args);
            } catch (Exception e) {
                e.printStackTrace();
            }

            pw.flush();
            pw.println("++++++++++++++++++++++++++++++++");
        }

        if (getCarrierPrivilegesTracker() != null) {
            pw.println("CarrierPrivilegesTracker:");
            getCarrierPrivilegesTracker().dump(fd, pw, args);
            pw.println("++++++++++++++++++++++++++++++++");
        }

        if (getLinkBandwidthEstimator() != null) {
            pw.println("LinkBandwidthEstimator:");
            getLinkBandwidthEstimator().dump(fd, pw, args);
            pw.println("++++++++++++++++++++++++++++++++");
        }

        pw.println("Phone Local Log: ");
        if (mLocalLog != null) {
            try {
                mLocalLog.dump(fd, pw, args);
            } catch (Exception e) {
                e.printStackTrace();
            }
            pw.flush();
            pw.println("++++++++++++++++++++++++++++++++");
        }
        if (mTelephonyAnalytics != null) {
            try {
                mTelephonyAnalytics.dump(fd, pw, args);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void logd(String s) {
        Rlog.d(LOG_TAG, "[" + mPhoneId + "] " + s);
    }

    private void logi(String s) {
        Rlog.i(LOG_TAG, "[" + mPhoneId + "] " + s);
    }

    private void loge(String s) {
        Rlog.e(LOG_TAG, "[" + mPhoneId + "] " + s);
    }

    private static String pii(String s) {
        return Rlog.pii(LOG_TAG, s);
    }
}
