Merge changes from topics "displayinfocontroller", "remove_displayinfo"

* changes:
  Remove DisplayInfo logic from DcTracker
  DisplayInfoController and NetworkTypeController skeleton implementation
diff --git a/src/java/com/android/internal/telephony/DeviceStateMonitor.java b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
index 30153ea..f940919 100644
--- a/src/java/com/android/internal/telephony/DeviceStateMonitor.java
+++ b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
@@ -35,6 +35,8 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.Registrant;
+import android.os.RegistrantList;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SignalThresholdInfo;
@@ -83,6 +85,8 @@
 
     private final LocalLog mLocalLog = new LocalLog(100);
 
+    private final RegistrantList mPhysicalChannelConfigRegistrants = new RegistrantList();
+
     private final NetworkRequest mWifiNetworkRequest =
             new NetworkRequest.Builder()
             .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
@@ -492,6 +496,13 @@
             newFilter |= IndicationFilter.BARRING_INFO;
         }
 
+        // notify PhysicalChannelConfig registrants if state changes
+        if ((newFilter & IndicationFilter.PHYSICAL_CHANNEL_CONFIG)
+                != (mUnsolicitedResponseFilter & IndicationFilter.PHYSICAL_CHANNEL_CONFIG)) {
+            mPhysicalChannelConfigRegistrants.notifyResult(
+                    (newFilter & IndicationFilter.PHYSICAL_CHANNEL_CONFIG) != 0);
+        }
+
         setUnsolResponseFilter(newFilter, false);
 
         // Pull barring info AFTER setting filter, the order matters
@@ -656,6 +667,28 @@
     }
 
     /**
+     * Register for PhysicalChannelConfig notifications changed. On change, msg.obj will be an
+     * AsyncResult with a boolean result. AsyncResult.result is true if notifications are enabled
+     * and false if they are disabled.
+     *
+     * @param h Handler to notify
+     * @param what msg.what when the message is delivered
+     * @param obj AsyncResult.userObj when the message is delivered
+     */
+    public void registerForPhysicalChannelConfigNotifChanged(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mPhysicalChannelConfigRegistrants.add(r);
+    }
+
+    /**
+     * Unregister for PhysicalChannelConfig notifications changed.
+     * @param h Handler to notify
+     */
+    public void unregisterForPhysicalChannelConfigNotifChanged(Handler h) {
+        mPhysicalChannelConfigRegistrants.remove(h);
+    }
+
+    /**
      * @param msg Debug message
      * @param logIntoLocalLog True if log into the local log
      */
diff --git a/src/java/com/android/internal/telephony/DisplayInfoController.java b/src/java/com/android/internal/telephony/DisplayInfoController.java
new file mode 100644
index 0000000..c73c1ea
--- /dev/null
+++ b/src/java/com/android/internal/telephony/DisplayInfoController.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 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 android.os.Handler;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.telephony.TelephonyDisplayInfo;
+
+import com.android.telephony.Rlog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * The DisplayInfoController updates and broadcasts all changes to {@link TelephonyDisplayInfo}.
+ * It manages all the information necessary for display purposes. Clients can register for display
+ * info changes via {@link #registerForTelephonyDisplayInfoChanged} and obtain the current
+ * TelephonyDisplayInfo via {@link #getTelephonyDisplayInfo}.
+ */
+public class DisplayInfoController extends Handler {
+    private static final String TAG = "DisplayInfoController";
+    private final Phone mPhone;
+    private final NetworkTypeController mNetworkTypeController;
+    private final RegistrantList mTelephonyDisplayInfoChangedRegistrants = new RegistrantList();
+    private TelephonyDisplayInfo mTelephonyDisplayInfo;
+
+    public DisplayInfoController(Phone phone) {
+        mPhone = phone;
+        mNetworkTypeController = new NetworkTypeController(phone, this);
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+    }
+
+    /**
+     * @return the current TelephonyDisplayInfo
+     */
+    public TelephonyDisplayInfo getTelephonyDisplayInfo() {
+        return mTelephonyDisplayInfo;
+    }
+
+    /**
+     * Update TelephonyDisplayInfo based on network type and override network type, received from
+     * NetworkTypeController.
+     */
+    public void updateTelephonyDisplayInfo() {
+        TelephonyDisplayInfo newDisplayInfo = new TelephonyDisplayInfo(
+                mPhone.getServiceState().getDataNetworkType(),
+                mNetworkTypeController.getOverrideNetworkType());
+        if (!newDisplayInfo.equals(mTelephonyDisplayInfo)) {
+            Rlog.d(TAG, "TelephonyDisplayInfo changed from " + mTelephonyDisplayInfo + " to "
+                    + newDisplayInfo);
+            mTelephonyDisplayInfo = newDisplayInfo;
+            mTelephonyDisplayInfoChangedRegistrants.notifyRegistrants();
+            mPhone.notifyDisplayInfoChanged(mTelephonyDisplayInfo);
+        }
+    }
+
+    /**
+     * Register for TelephonyDisplayInfo changed.
+     * @param h Handler to notify
+     * @param what msg.what when the message is delivered
+     * @param obj msg.obj when the message is delivered
+     */
+    public void registerForTelephonyDisplayInfoChanged(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mTelephonyDisplayInfoChangedRegistrants.add(r);
+    }
+
+    /**
+     * Unregister for TelephonyDisplayInfo changed.
+     * @param h Handler to notify
+     */
+    public void unregisterForTelephonyDisplayInfoChanged(Handler h) {
+        mTelephonyDisplayInfoChangedRegistrants.remove(h);
+    }
+
+    /**
+     * Dump the current state.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("DisplayInfoController:");
+        pw.println(" mPhone=" + mPhone.getPhoneName());
+        pw.println(" mTelephonyDisplayInfo=" + mTelephonyDisplayInfo.toString());
+        pw.flush();
+        pw.println(" ***************************************");
+        mNetworkTypeController.dump(fd, pw, args);
+        pw.flush();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 5d7fba4..29f57f4 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -264,8 +264,16 @@
                 this, this.mCi);
         mDataEnabledSettings = mTelephonyComponentFactory
                 .inject(DataEnabledSettings.class.getName()).makeDataEnabledSettings(this);
+        mDeviceStateMonitor = mTelephonyComponentFactory.inject(DeviceStateMonitor.class.getName())
+                .makeDeviceStateMonitor(this);
 
-        // DcTracker uses SST so needs to be created after it is instantiated
+        // DisplayInfoController creates an OverrideNetworkTypeController, which uses
+        // DeviceStateMonitor so needs to be crated after it is instantiated.
+        mDisplayInfoController = mTelephonyComponentFactory.inject(
+                DisplayInfoController.class.getName()).makeDisplayInfoController(this);
+
+        // DcTracker uses ServiceStateTracker and DisplayInfoController so needs to be created
+        // after they are instantiated
         for (int transport : mTransportManager.getAvailableTransports()) {
             mDcTrackers.put(transport, mTelephonyComponentFactory.inject(DcTracker.class.getName())
                     .makeDcTracker(this, transport));
@@ -279,9 +287,6 @@
                 EVENT_SET_CARRIER_DATA_ENABLED, null, false);
 
         mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
-        mDeviceStateMonitor = mTelephonyComponentFactory.inject(DeviceStateMonitor.class.getName())
-                .makeDeviceStateMonitor(this);
-
         mSST.registerForVoiceRegStateOrRatChanged(this, EVENT_VRS_OR_RAT_CHANGED, null);
 
         mSettingsObserver = new SettingsObserver(context, this);
@@ -586,6 +591,16 @@
     }
 
     @Override
+    public DeviceStateMonitor getDeviceStateMonitor() {
+        return mDeviceStateMonitor;
+    }
+
+    @Override
+    public DisplayInfoController getDisplayInfoController() {
+        return mDisplayInfoController;
+    }
+
+    @Override
     public void updateVoiceMail() {
         if (isPhoneTypeGsm()) {
             int countVoiceMessages = 0;
diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java
new file mode 100644
index 0000000..86b7902
--- /dev/null
+++ b/src/java/com/android/internal/telephony/NetworkTypeController.java
@@ -0,0 +1,956 @@
+/*
+ * Copyright (C) 2020 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 android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncResult;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.Annotation;
+import android.telephony.CarrierConfigManager;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.android.internal.util.IState;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.telephony.Rlog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The NetworkTypeController evaluates the override network type of {@link TelephonyDisplayInfo}
+ * and sends it to {@link DisplayInfoController}. The override network type can replace the signal
+ * icon displayed on the status bar. It is affected by changes in data RAT, NR state, NR frequency,
+ * data activity, physical channel config, and carrier configurations. Based on carrier configs,
+ * NetworkTypeController also allows timers between various 5G states to prevent flickering.
+ */
+public class NetworkTypeController extends StateMachine {
+    private static final boolean DBG = true;
+    private static final String TAG = "NetworkTypeController";
+    private static final String ICON_5G = "5g";
+    private static final String ICON_5G_PLUS = "5g_plus";
+    private static final String STATE_CONNECTED_MMWAVE = "connected_mmwave";
+    private static final String STATE_CONNECTED = "connected";
+    private static final String STATE_NOT_RESTRICTED_RRC_IDLE = "not_restricted_rrc_idle";
+    private static final String STATE_NOT_RESTRICTED_RRC_CON = "not_restricted_rrc_con";
+    private static final String STATE_RESTRICTED = "restricted";
+    private static final String STATE_ANY = "any";
+    private static final String STATE_LEGACY = "legacy";
+    private static final String[] ALL_STATES = { STATE_CONNECTED_MMWAVE, STATE_CONNECTED,
+            STATE_NOT_RESTRICTED_RRC_IDLE, STATE_NOT_RESTRICTED_RRC_CON, STATE_RESTRICTED };
+
+    /** Stop all timers and go to current state. */
+    public static final int EVENT_UPDATE = 0;
+    /** Quit after processing all existing messages. */
+    public static final int EVENT_QUIT = 1;
+    private static final int EVENT_DATA_RAT_CHANGED = 2;
+    private static final int EVENT_NR_STATE_CHANGED = 3;
+    private static final int EVENT_NR_FREQUENCY_CHANGED = 4;
+    private static final int EVENT_DATA_ACTIVITY_CHANGED = 5;
+    private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED = 6;
+    private static final int EVENT_CARRIER_CONFIG_CHANGED = 7;
+    private static final int EVENT_PRIMARY_TIMER_EXPIRED = 8;
+    private static final int EVENT_SECONDARY_TIMER_EXPIRED = 9;
+    private static final int[] ALL_EVENTS = { EVENT_DATA_RAT_CHANGED, EVENT_NR_STATE_CHANGED,
+            EVENT_NR_FREQUENCY_CHANGED, EVENT_DATA_ACTIVITY_CHANGED,
+            EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED, EVENT_CARRIER_CONFIG_CHANGED,
+            EVENT_PRIMARY_TIMER_EXPIRED, EVENT_SECONDARY_TIMER_EXPIRED };
+
+    private static final String[] sEvents = new String[EVENT_SECONDARY_TIMER_EXPIRED + 1];
+    static {
+        sEvents[EVENT_UPDATE] = "EVENT_UPDATE";
+        sEvents[EVENT_QUIT] = "EVENT_QUIT";
+        sEvents[EVENT_DATA_RAT_CHANGED] = "EVENT_DATA_RAT_CHANGED";
+        sEvents[EVENT_NR_STATE_CHANGED] = "EVENT_NR_STATE_CHANGED";
+        sEvents[EVENT_NR_FREQUENCY_CHANGED] = "EVENT_NR_FREQUENCY_CHANGED";
+        sEvents[EVENT_DATA_ACTIVITY_CHANGED] = "EVENT_DATA_ACTIVITY_CHANGED";
+        sEvents[EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED] =
+                "EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED";
+        sEvents[EVENT_CARRIER_CONFIG_CHANGED] = "EVENT_CARRIER_CONFIG_CHANGED";
+        sEvents[EVENT_PRIMARY_TIMER_EXPIRED] = "EVENT_PRIMARY_TIMER_EXPIRED";
+        sEvents[EVENT_SECONDARY_TIMER_EXPIRED] = "EVENT_SECONDARY_TIMER_EXPIRED";
+    }
+
+    private final Phone mPhone;
+    private final DisplayInfoController mDisplayInfoController;
+    private final TelephonyManager mTelephonyManager;
+    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
+                    && intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
+                    SubscriptionManager.INVALID_PHONE_INDEX) == mPhone.getPhoneId()
+                    && !intent.getBooleanExtra(CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK,
+                    false)) {
+                sendMessage(EVENT_CARRIER_CONFIG_CHANGED);
+            }
+        }
+    };
+    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        @Override
+        public void onDataActivity(int direction) {
+            sendMessage(EVENT_DATA_ACTIVITY_CHANGED);
+        }
+    };
+
+    private Map<String, OverrideTimerRule> mOverrideTimerRules = new HashMap<>();
+    private String mLteEnhancedPattern = "";
+    private int mOverrideNetworkType;
+    private boolean mIsPhysicalChannelConfigOn;
+    private boolean mIsPrimaryTimerActive;
+    private boolean mIsSecondaryTimerActive;
+    private String mPrimaryTimerState;
+    private String mSecondaryTimerState;
+    private String mPreviousState;
+
+    /**
+     * NetworkTypeController constructor.
+     *
+     * @param phone Phone object.
+     * @param displayInfoController DisplayInfoController to send override network types to.
+     */
+    public NetworkTypeController(Phone phone, DisplayInfoController displayInfoController) {
+        super(TAG, displayInfoController);
+        mPhone = phone;
+        mDisplayInfoController = displayInfoController;
+        mTelephonyManager = TelephonyManager.from(phone.getContext())
+                .createForSubscriptionId(phone.getSubId());
+        mOverrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+        mIsPhysicalChannelConfigOn = true;
+        addState(mDefaultState);
+        addState(mLegacyState, mDefaultState);
+        addState(mIdleState, mDefaultState);
+        addState(mLteConnectedState, mDefaultState);
+        addState(mNrConnectedState, mDefaultState);
+        setInitialState(mDefaultState);
+        registerForAllEvents();
+        parseCarrierConfigs();
+        start();
+    }
+
+    /**
+     * @return The current override network type, used to create TelephonyDisplayInfo in
+     * DisplayInfoController.
+     */
+    public @Annotation.OverrideNetworkType int getOverrideNetworkType() {
+        return mOverrideNetworkType;
+    }
+
+    private void registerForAllEvents() {
+        mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getHandler(),
+                EVENT_DATA_RAT_CHANGED, null);
+        mPhone.getServiceStateTracker().registerForNrStateChanged(getHandler(),
+                EVENT_NR_STATE_CHANGED, null);
+        mPhone.getServiceStateTracker().registerForNrFrequencyChanged(getHandler(),
+                EVENT_NR_FREQUENCY_CHANGED, null);
+        mPhone.getDeviceStateMonitor().registerForPhysicalChannelConfigNotifChanged(getHandler(),
+                EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED, null);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
+        if (mTelephonyManager != null) {
+            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_DATA_ACTIVITY);
+        }
+    }
+
+    private void unRegisterForAllEvents() {
+        mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getHandler());
+        mPhone.getServiceStateTracker().unregisterForNrStateChanged(getHandler());
+        mPhone.getServiceStateTracker().unregisterForNrFrequencyChanged(getHandler());
+        mPhone.mDeviceStateMonitor.unregisterForPhysicalChannelConfigNotifChanged(getHandler());
+        mPhone.getContext().unregisterReceiver(mIntentReceiver);
+        if (mTelephonyManager != null) {
+            mTelephonyManager.listen(mPhoneStateListener, 0);
+        }
+    }
+
+    private void parseCarrierConfigs() {
+        String nrIconConfiguration = CarrierConfigManager.getDefaultConfig().getString(
+                CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
+        String overrideTimerRule = CarrierConfigManager.getDefaultConfig().getString(
+                CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING);
+        String overrideSecondaryTimerRule = CarrierConfigManager.getDefaultConfig().getString(
+                CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING);
+        mLteEnhancedPattern = CarrierConfigManager.getDefaultConfig().getString(
+                CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
+
+        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+            if (b != null) {
+                if (b.getString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING) != null) {
+                    nrIconConfiguration = b.getString(
+                            CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
+                }
+                if (b.getString(CarrierConfigManager
+                        .KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING) != null) {
+                    overrideTimerRule = b.getString(
+                            CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING);
+                }
+                if (b.getString(CarrierConfigManager
+                        .KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING) != null) {
+                    overrideSecondaryTimerRule = b.getString(
+                            CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING);
+                }
+                if (b.getString(CarrierConfigManager
+                        .KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING) != null) {
+                    mLteEnhancedPattern = b.getString(
+                            CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
+                }
+            }
+        }
+        createTimerRules(nrIconConfiguration, overrideTimerRule, overrideSecondaryTimerRule);
+    }
+
+    private void createTimerRules(String icons, String timers, String secondaryTimers) {
+        Map<String, OverrideTimerRule> tempRules = new HashMap<>();
+        if (!TextUtils.isEmpty(icons)) {
+            // Format: "STATE:ICON,STATE2:ICON2"
+            for (String pair : icons.trim().split(",")) {
+                String[] kv = (pair.trim().toLowerCase()).split(":");
+                if (kv.length != 2) {
+                    if (DBG) loge("Invalid 5G icon configuration, config = " + pair);
+                    continue;
+                }
+                int icon = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+                if (kv[1].equals(ICON_5G)) {
+                    icon = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA;
+                } else if (kv[1].equals(ICON_5G_PLUS)) {
+                    icon = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
+                } else {
+                    if (DBG) loge("Invalid 5G icon = " + kv[1]);
+                }
+                tempRules.put(kv[0], new OverrideTimerRule(kv[0], icon));
+            }
+        }
+        // Ensure all states have an associated OverrideTimerRule and icon
+        for (String state : ALL_STATES) {
+            if (!tempRules.containsKey(state)) {
+                tempRules.put(state, new OverrideTimerRule(
+                        state, TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE));
+            }
+        }
+
+        if (!TextUtils.isEmpty(timers)) {
+            // Format: "FROM_STATE,TO_STATE,DURATION;FROM_STATE_2,TO_STATE_2,DURATION_2"
+            for (String triple : timers.trim().split(";")) {
+                String[] kv = (triple.trim().toLowerCase()).split(",");
+                if (kv.length != 3) {
+                    if (DBG) loge("Invalid 5G icon timer configuration, config = " + triple);
+                    continue;
+                }
+                int duration;
+                try {
+                    duration = Integer.parseInt(kv[2]);
+                } catch (NumberFormatException e) {
+                    continue;
+                }
+                if (kv[0].equals(STATE_ANY)) {
+                    for (String state : ALL_STATES) {
+                        OverrideTimerRule node = tempRules.get(state);
+                        node.addTimer(kv[1], duration);
+                    }
+                } else {
+                    OverrideTimerRule node = tempRules.get(kv[0]);
+                    node.addTimer(kv[1], duration);
+                }
+            }
+        }
+
+        if (!TextUtils.isEmpty(secondaryTimers)) {
+            // Format: "PRIMARY_STATE,TO_STATE,DURATION;PRIMARY_STATE_2,TO_STATE_2,DURATION_2"
+            for (String triple : secondaryTimers.trim().split(";")) {
+                String[] kv = (triple.trim().toLowerCase()).split(",");
+                if (kv.length != 3) {
+                    if (DBG) {
+                        loge("Invalid 5G icon secondary timer configuration, config = " + triple);
+                    }
+                    continue;
+                }
+                int duration;
+                try {
+                    duration = Integer.parseInt(kv[2]);
+                } catch (NumberFormatException e) {
+                    continue;
+                }
+                if (kv[0].equals(STATE_ANY)) {
+                    for (String state : ALL_STATES) {
+                        OverrideTimerRule node = tempRules.get(state);
+                        node.addSecondaryTimer(kv[1], duration);
+                    }
+                } else {
+                    OverrideTimerRule node = tempRules.get(kv[0]);
+                    node.addSecondaryTimer(kv[1], duration);
+                }
+            }
+        }
+
+        mOverrideTimerRules = tempRules;
+        if (DBG) log("mOverrideTimerRules: " + mOverrideTimerRules);
+    }
+
+    private void updateOverrideNetworkType() {
+        if (mIsPrimaryTimerActive || mIsSecondaryTimerActive) {
+            if (DBG) log("Skip updating override network type since timer is active.");
+            return;
+        }
+        int displayNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+        int dataNetworkType = mPhone.getServiceState().getDataNetworkType();
+        // NR display is not accurate when physical channel config notifications are off
+        if (mIsPhysicalChannelConfigOn
+                && (mPhone.getServiceState().getNrState() != NetworkRegistrationInfo.NR_STATE_NONE
+                || dataNetworkType == TelephonyManager.NETWORK_TYPE_NR)) {
+            // Process NR display network type
+            displayNetworkType = getNrDisplayType();
+            if (displayNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE) {
+                // Use LTE values if 5G values aren't defined
+                displayNetworkType = getLteDisplayType();
+            }
+        } else if (isLte(dataNetworkType)) {
+            // Process LTE display network type
+            displayNetworkType = getLteDisplayType();
+        }
+        mOverrideNetworkType = displayNetworkType;
+        mDisplayInfoController.updateTelephonyDisplayInfo();
+    }
+
+    private @Annotation.OverrideNetworkType int getNrDisplayType() {
+        // Icon display keys in order of priority
+        List<String> keys = new ArrayList<>();
+        // TODO: Update for NR SA
+        switch (mPhone.getServiceState().getNrState()) {
+            case NetworkRegistrationInfo.NR_STATE_CONNECTED:
+                if (isNrMmwave()) {
+                    keys.add(STATE_CONNECTED_MMWAVE);
+                }
+                keys.add(STATE_CONNECTED);
+                break;
+            case NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED:
+                keys.add(isDataActive() ? STATE_NOT_RESTRICTED_RRC_CON
+                        : STATE_NOT_RESTRICTED_RRC_IDLE);
+                break;
+            case NetworkRegistrationInfo.NR_STATE_RESTRICTED:
+                keys.add(STATE_RESTRICTED);
+                break;
+        }
+
+        for (String key : keys) {
+            OverrideTimerRule rule = mOverrideTimerRules.get(key);
+            if (rule != null && rule.mOverrideType
+                    != TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE) {
+                return rule.mOverrideType;
+            }
+        }
+        return TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+    }
+
+    private @Annotation.OverrideNetworkType int getLteDisplayType() {
+        int value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+        if (mPhone.getServiceState().getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE_CA
+                || mPhone.getServiceState().isUsingCarrierAggregation()) {
+            value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA;
+        }
+        if (isLteEnhancedAvailable()) {
+            value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO;
+        }
+        return value;
+    }
+
+    private boolean isLteEnhancedAvailable() {
+        if (TextUtils.isEmpty(mLteEnhancedPattern)) {
+            return false;
+        }
+        Pattern stringPattern = Pattern.compile(mLteEnhancedPattern);
+        for (String opName : new String[] {mPhone.getServiceState().getOperatorAlphaLongRaw(),
+                mPhone.getServiceState().getOperatorAlphaShortRaw()}) {
+            if (!TextUtils.isEmpty(opName)) {
+                Matcher matcher = stringPattern.matcher(opName);
+                if (matcher.find()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * The parent state for all other states.
+     */
+    private final class DefaultState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("DefaultState: process " + getEventName(msg.what));
+            switch (msg.what) {
+                case EVENT_UPDATE:
+                    resetAllTimers();
+                    transitionToCurrentState();
+                    break;
+                case EVENT_QUIT:
+                    resetAllTimers();
+                    unRegisterForAllEvents();
+                    quit();
+                    break;
+                case EVENT_DATA_RAT_CHANGED:
+                case EVENT_NR_STATE_CHANGED:
+                case EVENT_NR_FREQUENCY_CHANGED:
+                case EVENT_DATA_ACTIVITY_CHANGED:
+                    // ignored
+                    break;
+                case EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED:
+                    AsyncResult result = (AsyncResult) msg.obj;
+                    mIsPhysicalChannelConfigOn = (boolean) result.result;
+                    for (int event : ALL_EVENTS) {
+                        removeMessages(event);
+                    }
+                    if (!mIsPhysicalChannelConfigOn) {
+                        resetAllTimers();
+                    }
+                    transitionToCurrentState();
+                    break;
+                case EVENT_CARRIER_CONFIG_CHANGED:
+                    for (int event : ALL_EVENTS) {
+                        removeMessages(event);
+                    }
+                    parseCarrierConfigs();
+                    resetAllTimers();
+                    transitionToCurrentState();
+                    break;
+                case EVENT_PRIMARY_TIMER_EXPIRED:
+                    transitionWithSecondaryTimerTo((IState) msg.obj);
+                    break;
+                case EVENT_SECONDARY_TIMER_EXPIRED:
+                    mIsSecondaryTimerActive = false;
+                    mSecondaryTimerState = "";
+                    updateTimers();
+                    updateOverrideNetworkType();
+                    break;
+                default:
+                    throw new RuntimeException("Received invalid event: " + msg.what);
+            }
+            return HANDLED;
+        }
+    }
+
+    private final DefaultState mDefaultState = new DefaultState();
+
+    /**
+     * Device does not have NR available, due to any of the below reasons:
+     * <ul>
+     *   <li> LTE cell does not support EN-DC
+     *   <li> LTE cell supports EN-DC, but the use of NR is restricted
+     *   <li> Data network type is not LTE, NR NSA, or NR SA
+     * </ul>
+     * This is the initial state.
+     */
+    private final class LegacyState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log("Entering LegacyState");
+            updateTimers();
+            updateOverrideNetworkType();
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("LegacyState: process " + getEventName(msg.what));
+            updateTimers();
+            int rat = mPhone.getServiceState().getDataNetworkType();
+            switch (msg.what) {
+                case EVENT_DATA_RAT_CHANGED:
+                    if (rat == TelephonyManager.NETWORK_TYPE_NR || isLte(rat) && isNrConnected()) {
+                        transitionTo(mNrConnectedState);
+                    } else if (isLte(rat) && isNrNotRestricted()) {
+                        transitionWithTimerTo(isDataActive() ? mLteConnectedState : mIdleState);
+                    }
+                    break;
+                case EVENT_NR_STATE_CHANGED:
+                    if (isNrConnected()) {
+                        transitionTo(mNrConnectedState);
+                    } else if (isLte(rat) && isNrNotRestricted()) {
+                        transitionWithTimerTo(isDataActive() ? mLteConnectedState : mIdleState);
+                    } else if (isLte(rat) && isNrRestricted()) {
+                        updateOverrideNetworkType();
+                    }
+                    break;
+                case EVENT_NR_FREQUENCY_CHANGED:
+                case EVENT_DATA_ACTIVITY_CHANGED:
+                    // ignored
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+            return HANDLED;
+        }
+
+        @Override
+        public String getName() {
+            return isNrRestricted() ? STATE_RESTRICTED : STATE_LEGACY;
+        }
+    }
+
+    private final LegacyState mLegacyState = new LegacyState();
+
+    /**
+     * Device does not have any physical connection with the cell (RRC idle).
+     */
+    private final class IdleState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log("Entering IdleState");
+            updateTimers();
+            updateOverrideNetworkType();
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("IdleState: process " + getEventName(msg.what));
+            updateTimers();
+            switch (msg.what) {
+                case EVENT_DATA_RAT_CHANGED:
+                    int rat = mPhone.getServiceState().getDataNetworkType();
+                    if (rat == TelephonyManager.NETWORK_TYPE_NR) {
+                        transitionTo(mNrConnectedState);
+                    } else if (!isLte(rat) || !isNrNotRestricted()) {
+                        transitionWithTimerTo(mLegacyState);
+                    }
+                    break;
+                case EVENT_NR_STATE_CHANGED:
+                    if (isNrConnected()) {
+                        transitionTo(mNrConnectedState);
+                    } else if (!isNrNotRestricted()) {
+                        transitionWithTimerTo(mLegacyState);
+                    }
+                    break;
+                case EVENT_NR_FREQUENCY_CHANGED:
+                    // ignore
+                    break;
+                case EVENT_DATA_ACTIVITY_CHANGED:
+                    if (isDataActive()) {
+                        transitionWithTimerTo(mLteConnectedState);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+            return HANDLED;
+        }
+
+        @Override
+        public String getName() {
+            return STATE_NOT_RESTRICTED_RRC_IDLE;
+        }
+    }
+
+    private final IdleState mIdleState = new IdleState();
+
+    /**
+     * Device is connected to LTE as the primary cell (RRC connected).
+     */
+    private final class LteConnectedState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log("Entering LteConnectedState");
+            updateTimers();
+            updateOverrideNetworkType();
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("LteConnectedState: process " + getEventName(msg.what));
+            updateTimers();
+            switch (msg.what) {
+                case EVENT_DATA_RAT_CHANGED:
+                    int rat = mPhone.getServiceState().getDataNetworkType();
+                    if (rat == TelephonyManager.NETWORK_TYPE_NR) {
+                        transitionTo(mNrConnectedState);
+                    } else if (!isLte(rat) || !isNrNotRestricted()) {
+                        transitionWithTimerTo(mLegacyState);
+                    }
+                    break;
+                case EVENT_NR_STATE_CHANGED:
+                    if (isNrConnected()) {
+                        transitionTo(mNrConnectedState);
+                    } else if (!isNrNotRestricted()) {
+                        transitionWithTimerTo(mLegacyState);
+                    }
+                    break;
+                case EVENT_NR_FREQUENCY_CHANGED:
+                    // ignore
+                    break;
+                case EVENT_DATA_ACTIVITY_CHANGED:
+                    if (!isDataActive()) {
+                        transitionWithTimerTo(mIdleState);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+            return HANDLED;
+        }
+
+        @Override
+        public String getName() {
+            return STATE_NOT_RESTRICTED_RRC_CON;
+        }
+    }
+
+    private final LteConnectedState mLteConnectedState = new LteConnectedState();
+
+    /**
+     * Device is connected to 5G NR as the secondary cell.
+     */
+    private final class NrConnectedState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log("Entering NrConnectedState");
+            updateTimers();
+            updateOverrideNetworkType();
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("NrConnectedState: process " + getEventName(msg.what));
+            updateTimers();
+            int rat = mPhone.getServiceState().getDataNetworkType();
+            switch (msg.what) {
+                case EVENT_DATA_RAT_CHANGED:
+                    if (rat == TelephonyManager.NETWORK_TYPE_NR || isLte(rat) && isNrConnected()) {
+                        updateOverrideNetworkType();
+                    } else if (isLte(rat) && isNrNotRestricted()) {
+                        transitionWithTimerTo(isDataActive() ? mLteConnectedState : mIdleState);
+                    } else {
+                        transitionWithTimerTo(mLegacyState);
+                    }
+                    break;
+                case EVENT_NR_STATE_CHANGED:
+                    if (isLte(rat) && isNrNotRestricted()) {
+                        transitionWithTimerTo(isDataActive() ? mLteConnectedState : mIdleState);
+                    } else if (rat != TelephonyManager.NETWORK_TYPE_NR && !isNrConnected()) {
+                        transitionWithTimerTo(mLegacyState);
+                    }
+                    break;
+                case EVENT_NR_FREQUENCY_CHANGED:
+                    if (!isNrMmwave()) {
+                        transitionWithTimerTo(mNrConnectedState);
+                    } else {
+                        updateOverrideNetworkType();
+                    }
+                    break;
+                case EVENT_DATA_ACTIVITY_CHANGED:
+                    // ignore
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+            return HANDLED;
+        }
+
+        @Override
+        public String getName() {
+            return isNrMmwave() ? STATE_CONNECTED_MMWAVE : STATE_CONNECTED;
+        }
+    }
+
+    private final NrConnectedState mNrConnectedState = new NrConnectedState();
+
+    private void transitionWithTimerTo(IState destState) {
+        String destName = destState.getName();
+        OverrideTimerRule rule = mOverrideTimerRules.get(mPreviousState);
+        if (rule != null && rule.getTimer(destName) > 0) {
+            if (DBG) log("Primary timer started for state: " + mPreviousState);
+            mPrimaryTimerState = mPreviousState;
+            mPreviousState = getCurrentState().getName();
+            mIsPrimaryTimerActive = true;
+            sendMessageDelayed(EVENT_PRIMARY_TIMER_EXPIRED, destState,
+                    rule.getTimer(destName) * 1000);
+        }
+        transitionTo(destState);
+    }
+
+    private void transitionWithSecondaryTimerTo(IState destState) {
+        String destName = destState.getName();
+        OverrideTimerRule rule = mOverrideTimerRules.get(mPrimaryTimerState);
+        if (rule != null && rule.getSecondaryTimer(destName) > 0) {
+            String currentName = getCurrentState().getName();
+            if (DBG) log("Secondary timer started for state: " + currentName);
+            mSecondaryTimerState = currentName;
+            mPreviousState = currentName;
+            mIsSecondaryTimerActive = true;
+            sendMessageDelayed(EVENT_SECONDARY_TIMER_EXPIRED, destState,
+                    rule.getSecondaryTimer(destName) * 1000);
+        }
+        mIsPrimaryTimerActive = false;
+        transitionTo(destState);
+    }
+
+    private void transitionToCurrentState() {
+        int dataRat = mPhone.getServiceState().getDataNetworkType();
+        if (dataRat == TelephonyManager.NETWORK_TYPE_NR || isNrConnected()) {
+            transitionTo(mNrConnectedState);
+            mPreviousState = isNrMmwave() ? STATE_CONNECTED_MMWAVE : STATE_CONNECTED;
+        } else if (isLte(dataRat) && isNrNotRestricted()) {
+            if (isDataActive()) {
+                transitionTo(mLteConnectedState);
+                mPreviousState = STATE_NOT_RESTRICTED_RRC_CON;
+            } else {
+                transitionTo(mIdleState);
+                mPreviousState = STATE_NOT_RESTRICTED_RRC_IDLE;
+            }
+        } else {
+            transitionTo(mLegacyState);
+            mPreviousState = isNrRestricted() ? STATE_RESTRICTED : STATE_LEGACY;
+        }
+    }
+
+    private void updateTimers() {
+        String currentState = getCurrentState().getName();
+        if (mIsPrimaryTimerActive && mPrimaryTimerState.equals(currentState)) {
+            // remove primary timer if device goes back to the original state
+            if (DBG) {
+                log("Remove primary timer since primary state and current state equal: "
+                        + mPrimaryTimerState);
+            }
+            removeMessages(EVENT_PRIMARY_TIMER_EXPIRED);
+            mIsPrimaryTimerActive = false;
+            mPrimaryTimerState = "";
+        }
+
+        if (mIsSecondaryTimerActive && !mSecondaryTimerState.equals(currentState)) {
+            // remove secondary timer if devices is no longer in secondary timer state
+            if (DBG) {
+                log("Remove secondary timer since current state (" +  currentState
+                        + ") is no longer secondary timer state (" + mSecondaryTimerState + ").");
+            }
+            removeMessages(EVENT_SECONDARY_TIMER_EXPIRED);
+            mIsSecondaryTimerActive = false;
+            mSecondaryTimerState = "";
+        }
+    }
+
+    private void resetAllTimers() {
+        removeMessages(EVENT_PRIMARY_TIMER_EXPIRED);
+        removeMessages(EVENT_SECONDARY_TIMER_EXPIRED);
+        mIsPrimaryTimerActive = false;
+        mIsSecondaryTimerActive = false;
+        mPrimaryTimerState = "";
+        mSecondaryTimerState = "";
+    }
+
+    /**
+     * Private class defining timer rules between states to prevent flickering. These rules are
+     * created in {@link #parseCarrierConfigs()} based on various carrier configs.
+     */
+    private class OverrideTimerRule {
+        /** The 5G state this timer rule applies for. See {@link #ALL_STATES}. */
+        final String mState;
+
+        /**
+         * The override network type associated with this 5G state. This is the icon that will be
+         * displayed on the status bar. An override type of NONE will display the LTE value instead.
+         */
+        final int mOverrideType;
+
+        /**
+         * A map of destination states and associated timers. If the 5G state changes from mState
+         * to the destination state, keep the override type until either the primary timer expires
+         * or mState is regained.
+         */
+        final Map<String, Integer> mPrimaryTimers;
+
+        /**
+         * A map of secondary states and associated timers. After the primary timer expires, keep
+         * the override type until either the secondary timer expires or the device is no longer in
+         * the secondary state.
+         */
+        final Map<String, Integer> mSecondaryTimers;
+
+        OverrideTimerRule(String state, int overrideType) {
+            mState = state;
+            mOverrideType = overrideType;
+            mPrimaryTimers = new HashMap<>();
+            mSecondaryTimers = new HashMap<>();
+        }
+
+        /**
+         * Add a primary timer.
+         * @param destination Transitions from mState to the destination state.
+         * @param duration How long to keep the override type after transition to destination state.
+         */
+        public void addTimer(String destination, int duration) {
+            mPrimaryTimers.put(destination, duration);
+        }
+
+        /**
+         * Add a secondary timer
+         * @param secondaryState Stays in secondaryState after primary timer expires.
+         * @param duration How long to keep the override type while in secondaryState.
+         */
+        public void addSecondaryTimer(String secondaryState, int duration) {
+            mSecondaryTimers.put(secondaryState, duration);
+        }
+
+        /**
+         * @return Primary timer duration from mState to destination state, or 0 if not defined.
+         */
+        public int getTimer(String destination) {
+            Integer timer = mPrimaryTimers.get(destination);
+            timer = timer == null ? mPrimaryTimers.get(STATE_ANY) : timer;
+            return timer == null ? 0 : timer;
+        }
+
+        /**
+         * @return Secondary timer duration for secondaryState, or 0 if not defined.
+         */
+        public int getSecondaryTimer(String secondaryState) {
+            Integer secondaryTimer = mSecondaryTimers.get(secondaryState);
+            secondaryTimer = secondaryTimer == null
+                    ? mSecondaryTimers.get(STATE_ANY) : secondaryTimer;
+            return secondaryTimer == null ? 0 : secondaryTimer;
+        }
+
+        @Override
+        public String toString() {
+            return "{mState=" + mState
+                    + ", mOverrideType="
+                    + TelephonyDisplayInfo.overrideNetworkTypeToString(mOverrideType)
+                    + ", mPrimaryTimers=" + mPrimaryTimers
+                    + ", mSecondaryTimers=" + mSecondaryTimers + "}";
+        }
+    }
+
+    private boolean isNrConnected() {
+        return mPhone.getServiceState().getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED;
+    }
+
+    private boolean isNrNotRestricted() {
+        return mPhone.getServiceState().getNrState()
+                == NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED;
+    }
+
+    private boolean isNrRestricted() {
+        return mPhone.getServiceState().getNrState()
+                == NetworkRegistrationInfo.NR_STATE_RESTRICTED;
+    }
+
+    private boolean isNrMmwave() {
+        return mPhone.getServiceState().getNrFrequencyRange()
+                == ServiceState.FREQUENCY_RANGE_MMWAVE;
+    }
+
+    private boolean isLte(int rat) {
+        return rat == TelephonyManager.NETWORK_TYPE_LTE
+                || rat == TelephonyManager.NETWORK_TYPE_LTE_CA;
+    }
+
+    private boolean isDataActive() {
+        PhoneInternalInterface.DataActivityState activity = mPhone.getDataActivityState();
+        return activity != PhoneInternalInterface.DataActivityState.DORMANT
+                && activity != PhoneInternalInterface.DataActivityState.NONE;
+    }
+
+    private String getEventName(int event) {
+        try {
+            return sEvents[event];
+        } catch (ArrayIndexOutOfBoundsException e) {
+            return "EVENT_NOT_DEFINED";
+        }
+    }
+
+    protected void log(String s) {
+        Rlog.d(TAG, s);
+    }
+
+    protected void loge(String s) {
+        Rlog.e(TAG, s);
+    }
+
+    @Override
+    public String toString() {
+        return "mOverrideTimerRules=" + mOverrideTimerRules.toString()
+                + ", mLteEnhancedPattern=" + mLteEnhancedPattern
+                + ", mIsPhysicalChannelConfigOn=" + mIsPhysicalChannelConfigOn
+                + ", mIsPrimaryTimerActive=" + mIsPrimaryTimerActive
+                + ", mIsSecondaryTimerActive=" + mIsSecondaryTimerActive
+                + ", mPrimaryTimerState=" + mPrimaryTimerState
+                + ", mSecondaryTimerState=" + mSecondaryTimerState
+                + ", mPreviousState=" + mPreviousState;
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
+        pw.print("NetworkTypeController: ");
+        super.dump(fd, pw, args);
+        pw.flush();
+        pw.increaseIndent();
+        pw.println("mOverrideTimerRules=" + mOverrideTimerRules.toString());
+        pw.println("mLteEnhancedPattern=" + mLteEnhancedPattern);
+        pw.println("mIsPhysicalChannelConfigOn=" + mIsPhysicalChannelConfigOn);
+        pw.println("mIsPrimaryTimerActive=" + mIsPrimaryTimerActive);
+        pw.println("mIsSecondaryTimerActive=" + mIsSecondaryTimerActive);
+        pw.println("mPrimaryTimerState=" + mPrimaryTimerState);
+        pw.println("mSecondaryTimerState=" + mSecondaryTimerState);
+        pw.println("mPreviousState=" + mPreviousState);
+        pw.decreaseIndent();
+        pw.flush();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 5af1c93..fe89b52 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -315,6 +315,7 @@
     private final String mActionDetached;
     private final String mActionAttached;
     protected DeviceStateMonitor mDeviceStateMonitor;
+    protected DisplayInfoController mDisplayInfoController;
     protected TransportManager mTransportManager;
     protected DataEnabledSettings mDataEnabledSettings;
     // Used for identify the carrier of current subscription
@@ -1806,6 +1807,20 @@
     }
 
     /**
+     * Retrieves the DeviceStateMonitor of the phone instance.
+     */
+    public DeviceStateMonitor getDeviceStateMonitor() {
+        return null;
+    }
+
+    /**
+     * Retrieves the DisplayInfoController of the phone instance.
+     */
+    public DisplayInfoController getDisplayInfoController() {
+        return null;
+    }
+
+    /**
      * Update voice activation state
      */
     public void setVoiceActivationState(int state) {
@@ -4344,6 +4359,17 @@
             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);
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index d851f19..5a88887 100755
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -2006,15 +2006,6 @@
         return cdmaRoaming && !isSameOperatorNameFromSimAndSS(s);
     }
 
-    private boolean isNrStateChanged(
-            NetworkRegistrationInfo oldRegState, NetworkRegistrationInfo newRegState) {
-        if (oldRegState == null || newRegState == null) {
-            return oldRegState != newRegState;
-        }
-
-        return oldRegState.getNrState() != newRegState.getNrState();
-    }
-
     private boolean updateNrFrequencyRangeFromPhysicalChannelConfigs(
             List<PhysicalChannelConfig> physicalChannelConfigs, ServiceState ss) {
         int newFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN;
@@ -3268,11 +3259,7 @@
         boolean hasNrFrequencyRangeChanged =
                 mSS.getNrFrequencyRange() != mNewSS.getNrFrequencyRange();
 
-        boolean hasNrStateChanged = isNrStateChanged(
-                mSS.getNetworkRegistrationInfo(
-                        NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkType.EUTRAN),
-                mNewSS.getNetworkRegistrationInfo(
-                        NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkType.EUTRAN));
+        boolean hasNrStateChanged = mSS.getNrState() != mNewSS.getNrState();
 
         final List<CellIdentity> prioritizedCids = getPrioritizedCellIdentities(mNewSS);
 
@@ -3588,6 +3575,14 @@
             mPhone.notifyLocationChanged(getCellIdentity());
         }
 
+        if (hasNrStateChanged) {
+            mNrStateChangedRegistrants.notifyRegistrants();
+        }
+
+        if (hasNrFrequencyRangeChanged) {
+            mNrFrequencyChangedRegistrants.notifyRegistrants();
+        }
+
         if (mPhone.isPhoneTypeGsm()) {
             if (!isGprsConsistent(mSS.getDataRegistrationState(), mSS.getState())) {
                 if (!mStartedGprsRegCheck && !mReportedGprsNoReg) {
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index bdc28a5..9ed42ad 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -433,6 +433,13 @@
         return PhoneSwitcher.make(maxDataAttachModemCount, context, looper);
     }
 
+    /**
+     * Create a new DisplayInfoController.
+     */
+    public DisplayInfoController makeDisplayInfoController(Phone phone) {
+        return new DisplayInfoController(phone);
+    }
+
     public MultiSimSettingController initMultiSimSettingController(Context c,
             SubscriptionController sc) {
         return MultiSimSettingController.init(c, sc);
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index bbcd93a..8463deb 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -131,8 +131,6 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 /**
@@ -314,9 +312,9 @@
     private final LocalLog mDataRoamingLeakageLog = new LocalLog(50);
     private final LocalLog mApnSettingsInitializationLog = new LocalLog(50);
 
-    /* Default for 5G connection reevaluation alarm durations */
-    private int mHysteresisTimeSec = 0;
+    /* 5G connection reevaluation watchdog alarm constants */
     private long mWatchdogTimeMs = 1000 * 60 * 60;
+    private boolean mWatchdog = false;
 
     /* Default for whether 5G frequencies are considered unmetered */
     private boolean mNrNsaAllUnmetered = false;
@@ -327,21 +325,9 @@
     private boolean mNrSaSub6Unmetered = false;
     private boolean mRoamingUnmetered = false;
 
-    /* Used to check whether 5G timers are currently active and waiting to go off */
-    private boolean mHysteresis = false;
-    private boolean mWatchdog = false;
-
     /* List of SubscriptionPlans, updated on SubscriptionManager.setSubscriptionPlans */
     private List<SubscriptionPlan> mSubscriptionPlans = null;
 
-    /* Used to check whether phone was recently connected to 5G. */
-    private boolean m5GWasConnected = false;
-
-    /* Used to determine TelephonyDisplayInfo to send to SysUI. */
-    private TelephonyDisplayInfo mTelephonyDisplayInfo = null;
-    private final Map<String, Integer> m5GIconMapping = new HashMap<>();
-    private String mDataIconPattern = "";
-
     @SimState
     private int mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
 
@@ -357,16 +343,12 @@
                 stopNetStatPoll();
                 startNetStatPoll();
                 restartDataStallAlarm();
-                reevaluateUnmeteredConnections();
             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                 if (DBG) log("screen off");
                 mIsScreenOn = false;
                 stopNetStatPoll();
                 startNetStatPoll();
                 restartDataStallAlarm();
-                stopHysteresisAlarm();
-                stopWatchdogAlarm();
-                setDataConnectionUnmetered(false);
             } else if (action.equals(INTENT_DATA_STALL_ALARM)) {
                 onActionIntentDataStallAlarm(intent);
             } else if (action.equals(INTENT_PROVISIONING_APN_ALARM)) {
@@ -780,8 +762,6 @@
                 DctConstants.EVENT_PS_RESTRICT_DISABLED, null);
         mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(mTransportType, this,
                 DctConstants.EVENT_DATA_RAT_CHANGED, null);
-        // listens for PhysicalChannelConfig changes
-        mPhone.registerForServiceStateChanged(this, DctConstants.EVENT_SERVICE_STATE_CHANGED, null);
     }
 
     public void unregisterServiceStateTrackerEvents() {
@@ -791,9 +771,7 @@
         mPhone.getServiceStateTracker().unregisterForDataRoamingOff(this);
         mPhone.getServiceStateTracker().unregisterForPsRestrictedEnabled(this);
         mPhone.getServiceStateTracker().unregisterForPsRestrictedDisabled(this);
-        mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(mTransportType,
-                this);
-        mPhone.unregisterForServiceStateChanged(this);
+        mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(mTransportType, this);
     }
 
     private void registerForAllEvents() {
@@ -813,6 +791,8 @@
                 DctConstants.EVENT_VOICE_CALL_ENDED, null);
         mPhone.getCallTracker().registerForVoiceCallStarted(this,
                 DctConstants.EVENT_VOICE_CALL_STARTED, null);
+        mPhone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged(this,
+                DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED, null);
         registerServiceStateTrackerEvents();
         mDataServiceManager.registerForServiceBindingChanged(this,
                 DctConstants.EVENT_DATA_SERVICE_BINDING_CHANGED, null);
@@ -859,9 +839,9 @@
 
         mPhone.getCallTracker().unregisterForVoiceCallEnded(this);
         mPhone.getCallTracker().unregisterForVoiceCallStarted(this);
+        mPhone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(this);
         unregisterServiceStateTrackerEvents();
         mDataServiceManager.unregisterForServiceBindingChanged(this);
-
         mDataEnabledSettings.unregisterForDataEnabledChanged(this);
         mDataEnabledSettings.unregisterForDataEnabledOverrideChanged(this);
     }
@@ -896,7 +876,6 @@
         log("setActivity = " + activity);
         mActivity = activity;
         mPhone.notifyDataActivity();
-        updateDisplayInfo();
     }
 
     public void requestNetwork(NetworkRequest networkRequest, @RequestNetworkType int type,
@@ -3828,21 +3807,13 @@
             case DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED:
                 onDataEnabledOverrideRulesChanged();
                 break;
-            case DctConstants.EVENT_SERVICE_STATE_CHANGED:
-                if (!reevaluateUnmeteredConnections()) {
-                    // always update on ServiceState changed so MobileSignalController gets
-                    // accurate display info
-                    mPhone.notifyDisplayInfoChanged(mTelephonyDisplayInfo);
-                }
-                break;
-            case DctConstants.EVENT_5G_TIMER_HYSTERESIS:
-                reevaluateUnmeteredConnections();
-                mHysteresis = false;
-                break;
-            case DctConstants.EVENT_5G_TIMER_WATCHDOG:
+            case DctConstants.EVENT_NR_TIMER_WATCHDOG:
                 mWatchdog = false;
                 reevaluateUnmeteredConnections();
                 break;
+            case DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED:
+                reevaluateUnmeteredConnections();
+                break;
             case DctConstants.EVENT_CARRIER_CONFIG_CHANGED:
                 onCarrierConfigChanged();
                 break;
@@ -3938,26 +3909,6 @@
         return mBandwidths.get(ratName);
     }
 
-    private void update5GIconMapping(String config) {
-        synchronized (m5GIconMapping) {
-            m5GIconMapping.clear();
-            for (String pair : config.trim().split(",")) {
-                String[] kv = (pair.trim().toLowerCase()).split(":");
-                if (kv.length != 2) {
-                    if (DBG) log("Invalid 5G icon configuration, config = " + pair);
-                    continue;
-                }
-                int value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
-                if (kv[1].equals("5g")) {
-                    value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA;
-                } else if (kv[1].equals("5g_plus")) {
-                    value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
-                }
-                m5GIconMapping.put(kv[0], value);
-            }
-        }
-    }
-
     @VisibleForTesting
     public boolean shouldAutoAttach() {
         if (mAutoAttachEnabled.get()) return true;
@@ -4032,49 +3983,25 @@
         }
     }
 
-    private boolean reevaluateUnmeteredConnections() {
+    private void reevaluateUnmeteredConnections() {
         log("reevaluateUnmeteredConnections");
-        int networkType = ServiceState.rilRadioTechnologyToNetworkType(getDataRat());
-        boolean nrUnmetered = isNetworkTypeUnmetered(NETWORK_TYPE_NR);
-        boolean nrNsaUnmetered = isNrNsaFrequencyRangeUnmetered();
-        boolean nrSaUnmetered = isNrSaFrequencyRangeUnmetered();
-        if ((nrUnmetered || nrNsaUnmetered || nrSaUnmetered)
+        int rat = mPhone.getDisplayInfoController().getTelephonyDisplayInfo().getNetworkType();
+        int override = mPhone.getDisplayInfoController().getTelephonyDisplayInfo()
+                .getOverrideNetworkType();
+        boolean nrPlanUnmetered = isNetworkTypeUnmetered(NETWORK_TYPE_NR) && (rat == NETWORK_TYPE_NR
+                || override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA
+                || override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE);
+        if ((nrPlanUnmetered || isNrNsaFrequencyRangeUnmetered() || isNrSaFrequencyRangeUnmetered())
                 && !mPhone.getServiceState().getRoaming() || mRoamingUnmetered) {
             if (DBG) log("NR is unmetered");
-            if ((nrUnmetered || nrNsaUnmetered) && mPhone.getServiceState().getNrState()
-                    == NetworkRegistrationInfo.NR_STATE_CONNECTED
-                    || (nrUnmetered || nrSaUnmetered) && networkType == NETWORK_TYPE_NR) {
-                if (!m5GWasConnected) { // 4G -> 5G
-                    stopHysteresisAlarm();
-                    setDataConnectionUnmetered(true);
-                }
-                if (!mWatchdog) {
-                    startWatchdogAlarm();
-                }
-                m5GWasConnected = true;
-            } else {
-                if (m5GWasConnected) { // 5G -> 4G
-                    if (!mHysteresis && !startHysteresisAlarm()) {
-                        // hysteresis is not active but carrier does not support hysteresis
-                        stopWatchdogAlarm();
-                        setDataConnectionUnmetered(isNetworkTypeUnmetered(networkType));
-                    }
-                    m5GWasConnected = false;
-                } else { // 4G -> 4G
-                    if (!hasMessages(DctConstants.EVENT_5G_TIMER_HYSTERESIS)) {
-                        stopWatchdogAlarm();
-                        setDataConnectionUnmetered(isNetworkTypeUnmetered(networkType));
-                    }
-                    // do nothing if waiting for hysteresis alarm to go off
-                }
+            setDataConnectionUnmetered(true);
+            if (!mWatchdog) {
+                startWatchdogAlarm();
             }
         } else {
             stopWatchdogAlarm();
-            stopHysteresisAlarm();
-            setDataConnectionUnmetered(isNetworkTypeUnmetered(networkType));
-            m5GWasConnected = false;
+            setDataConnectionUnmetered(isNetworkTypeUnmetered(rat));
         }
-        return updateDisplayInfo();
     }
 
     private void setDataConnectionUnmetered(boolean isUnmetered) {
@@ -4121,18 +4048,24 @@
     }
 
     private boolean isNrNsaFrequencyRangeUnmetered() {
+        int override = mPhone.getDisplayInfoController().getTelephonyDisplayInfo()
+                .getOverrideNetworkType();
         if (mNrNsaMmwaveUnmetered || mNrNsaSub6Unmetered) {
-            int frequencyRange = mPhone.getServiceState().getNrFrequencyRange();
-            boolean mmwave = frequencyRange == ServiceState.FREQUENCY_RANGE_MMWAVE;
-            // frequency range LOW, MID, or HIGH
-            boolean sub6 = frequencyRange != ServiceState.FREQUENCY_RANGE_UNKNOWN && !mmwave;
-            return mNrNsaMmwaveUnmetered && mmwave || mNrNsaSub6Unmetered && sub6;
+            return (mNrNsaMmwaveUnmetered
+                    && override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE)
+                    || (mNrNsaSub6Unmetered
+                    && override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA);
         } else {
-            return mNrNsaAllUnmetered;
+            return mNrNsaAllUnmetered
+                    && (override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE
+                    || override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA);
         }
     }
 
     private boolean isNrSaFrequencyRangeUnmetered() {
+        if (ServiceState.rilRadioTechnologyToNetworkType(getDataRat()) != NETWORK_TYPE_NR) {
+            return false;
+        }
         if (mNrSaMmwaveUnmetered || mNrSaSub6Unmetered) {
             int frequencyRange = mPhone.getServiceState().getNrFrequencyRange();
             boolean mmwave = frequencyRange == ServiceState.FREQUENCY_RANGE_MMWAVE;
@@ -4144,94 +4077,6 @@
         }
     }
 
-    private boolean updateDisplayInfo() {
-        int displayNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
-        int dataNetworkType = mPhone.getServiceState().getDataNetworkType();
-        if (mPhone.getServiceState().getNrState() != NetworkRegistrationInfo.NR_STATE_NONE
-                || dataNetworkType == TelephonyManager.NETWORK_TYPE_NR || mHysteresis) {
-            // process NR display network type
-            displayNetworkType = getNrDisplayType();
-            if (displayNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE) {
-                // use LTE values if 5G values aren't defined
-                displayNetworkType = getLteDisplayType();
-            }
-        } else if (dataNetworkType == TelephonyManager.NETWORK_TYPE_LTE
-                || dataNetworkType == TelephonyManager.NETWORK_TYPE_LTE_CA) {
-            // process LTE display network type
-            displayNetworkType = getLteDisplayType();
-        }
-        TelephonyDisplayInfo telephonyDisplayInfo =
-                new TelephonyDisplayInfo(dataNetworkType, displayNetworkType);
-        if (!telephonyDisplayInfo.equals(mTelephonyDisplayInfo)) {
-            log("Display info changed: " + telephonyDisplayInfo);
-            mTelephonyDisplayInfo = telephonyDisplayInfo;
-            mPhone.notifyDisplayInfoChanged(telephonyDisplayInfo);
-            return true;
-        }
-        return false;
-    }
-
-    private int getNrDisplayType() {
-        // icon display keys in order of priority
-        List<String> keys = new ArrayList<>();
-        switch (mPhone.getServiceState().getNrState()) {
-            case NetworkRegistrationInfo.NR_STATE_CONNECTED:
-                if (mPhone.getServiceState().getNrFrequencyRange()
-                        == ServiceState.FREQUENCY_RANGE_MMWAVE) {
-                    keys.add("connected_mmwave");
-                }
-                keys.add("connected");
-                break;
-            case NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED:
-                if (mActivity == DctConstants.Activity.DORMANT) {
-                    keys.add("not_restricted_rrc_idle");
-                } else {
-                    keys.add("not_restricted_rrc_con");
-                }
-                break;
-            case NetworkRegistrationInfo.NR_STATE_RESTRICTED:
-                keys.add("restricted");
-                break;
-        }
-
-        for (String key : keys) {
-            if (m5GIconMapping.containsKey(key)
-                    && m5GIconMapping.get(key) != TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE) {
-                return m5GIconMapping.get(key);
-            }
-        }
-        return TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
-    }
-
-    private int getLteDisplayType() {
-        int value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
-        if (mPhone.getServiceState().getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE_CA
-                || mPhone.getServiceState().isUsingCarrierAggregation()) {
-            value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA;
-        }
-        if (isLteEnhancedAvailable()) {
-            value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO;
-        }
-        return value;
-    }
-
-    private boolean isLteEnhancedAvailable() {
-        if (TextUtils.isEmpty(mDataIconPattern)) {
-            return false;
-        }
-        Pattern stringPattern = Pattern.compile(mDataIconPattern);
-        for (String opName : new String[] {mPhone.getServiceState().getOperatorAlphaLongRaw(),
-                mPhone.getServiceState().getOperatorAlphaShortRaw()}) {
-            if (!TextUtils.isEmpty(opName)) {
-                Matcher matcher = stringPattern.matcher(opName);
-                if (matcher.find()) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     protected void log(String s) {
         Rlog.d(mLogTag, s);
     }
@@ -4627,7 +4472,6 @@
                     log("updateDataActivity: newActivity=" + newActivity);
                 mActivity = newActivity;
                 mPhone.notifyDataActivity();
-                updateDisplayInfo();
             }
         }
     }
@@ -5072,32 +4916,15 @@
     }
 
     /**
-     * 5G connection reevaluation alarms
+     * 5G connection reevaluation alarm
      */
-    private boolean startHysteresisAlarm() {
-        if (mHysteresisTimeSec > 0) {
-            // only create hysteresis alarm if CarrierConfig allows it
-            sendMessageDelayed(obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS),
-                    mHysteresisTimeSec * 1000);
-            mHysteresis = true;
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    private void stopHysteresisAlarm() {
-        removeMessages(DctConstants.EVENT_5G_TIMER_HYSTERESIS);
-        mHysteresis = false;
-    }
-
     private void startWatchdogAlarm() {
-        sendMessageDelayed(obtainMessage(DctConstants.EVENT_5G_TIMER_WATCHDOG), mWatchdogTimeMs);
+        sendMessageDelayed(obtainMessage(DctConstants.EVENT_NR_TIMER_WATCHDOG), mWatchdogTimeMs);
         mWatchdog = true;
     }
 
     private void stopWatchdogAlarm() {
-        removeMessages(DctConstants.EVENT_5G_TIMER_WATCHDOG);
+        removeMessages(DctConstants.EVENT_NR_TIMER_WATCHDOG);
         mWatchdog = false;
     }
 
@@ -5189,50 +5016,28 @@
         return ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
     }
 
-    // TODO: Move icon related to display info controller.
     private void read5GConfiguration() {
         if (DBG) log("read5GConfiguration");
-        String nr5GIconConfiguration = CarrierConfigManager.getDefaultConfig().getString(
-                CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
         String[] bandwidths = CarrierConfigManager.getDefaultConfig().getStringArray(
                 CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY);
         boolean useLte = false;
-        mDataIconPattern = CarrierConfigManager.getDefaultConfig().getString(
-                CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
         CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
                 .getSystemService(Context.CARRIER_CONFIG_SERVICE);
         if (configManager != null) {
             PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
             if (b != null) {
-                if (b.getString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING)
-                        != null) {
-                    nr5GIconConfiguration = b.getString(
-                            CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
-                }
-                if (b.getString(CarrierConfigManager
-                        .KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING) != null) {
-                    mDataIconPattern = b.getString(
-                            CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
-                }
-                if (b.getStringArray(CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY)
-                        != null) {
-                    bandwidths = b.getStringArray(
-                            CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY);
+                if (b.getStringArray(CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY) != null) {
+                    bandwidths = b.getStringArray(CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY);
                 }
                 useLte = b.getBoolean(CarrierConfigManager
                         .KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPSTREAM_BOOL);
-                mHysteresisTimeSec = b.getInt(
-                        CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT);
-                mWatchdogTimeMs = b.getLong(
-                        CarrierConfigManager.KEY_5G_WATCHDOG_TIME_MS_LONG);
-                mNrNsaAllUnmetered = b.getBoolean(
-                        CarrierConfigManager.KEY_UNMETERED_NR_NSA_BOOL);
+                mWatchdogTimeMs = b.getLong(CarrierConfigManager.KEY_5G_WATCHDOG_TIME_MS_LONG);
+                mNrNsaAllUnmetered = b.getBoolean(CarrierConfigManager.KEY_UNMETERED_NR_NSA_BOOL);
                 mNrNsaMmwaveUnmetered = b.getBoolean(
                         CarrierConfigManager.KEY_UNMETERED_NR_NSA_MMWAVE_BOOL);
                 mNrNsaSub6Unmetered = b.getBoolean(
                         CarrierConfigManager.KEY_UNMETERED_NR_NSA_SUB6_BOOL);
-                mNrSaAllUnmetered = b.getBoolean(
-                        CarrierConfigManager.KEY_UNMETERED_NR_SA_BOOL);
+                mNrSaAllUnmetered = b.getBoolean(CarrierConfigManager.KEY_UNMETERED_NR_SA_BOOL);
                 mNrSaMmwaveUnmetered = b.getBoolean(
                         CarrierConfigManager.KEY_UNMETERED_NR_SA_MMWAVE_BOOL);
                 mNrSaSub6Unmetered = b.getBoolean(
@@ -5241,8 +5046,6 @@
                         CarrierConfigManager.KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL);
             }
         }
-
         updateLinkBandwidths(bandwidths, useLte);
-        update5GIconMapping(nr5GIconConfiguration);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
new file mode 100644
index 0000000..fbca14a
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
@@ -0,0 +1,588 @@
+/*
+ * Copyright (C) 2020 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 org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.util.IState;
+import com.android.internal.util.StateMachine;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Method;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NetworkTypeControllerTest extends TelephonyTest {
+    // private constants copied over from NetworkTypeController
+    private static final int EVENT_DATA_RAT_CHANGED = 2;
+    private static final int EVENT_NR_STATE_CHANGED = 3;
+    private static final int EVENT_NR_FREQUENCY_CHANGED = 4;
+    private static final int EVENT_DATA_ACTIVITY_CHANGED = 5;
+    private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED = 6;
+    private static final int EVENT_CARRIER_CONFIG_CHANGED = 7;
+    private static final int EVENT_PRIMARY_TIMER_EXPIRED = 8;
+    private static final int EVENT_SECONDARY_TIMER_EXPIRED = 9;
+
+    private NetworkTypeController mNetworkTypeController;
+    private PersistableBundle mBundle;
+
+    private IState getCurrentState() throws Exception {
+        Method method = StateMachine.class.getDeclaredMethod("getCurrentState");
+        method.setAccessible(true);
+        return (IState) method.invoke(mNetworkTypeController);
+    }
+
+    private void updateOverrideNetworkType() throws Exception {
+        Method method = NetworkTypeController.class.getDeclaredMethod("updateOverrideNetworkType");
+        method.setAccessible(true);
+        method.invoke(mNetworkTypeController);
+    }
+
+    private void broadcastCarrierConfigs() {
+        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
+        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId());
+        mContext.sendBroadcast(intent);
+        processAllMessages();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        mBundle = mContextFixture.getCarrierConfigBundle();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING,
+                "connected_mmwave:5G_Plus,connected:5G,not_restricted_rrc_idle:5G,"
+                        + "not_restricted_rrc_con:5G");
+        broadcastCarrierConfigs();
+
+        replaceInstance(Handler.class, "mLooper", mDisplayInfoController, Looper.myLooper());
+        mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mNetworkTypeController.getHandler().removeCallbacksAndMessages(null);
+        mNetworkTypeController = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void testUpdateOverrideNetworkTypeNrNsa() throws Exception {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+
+        // not NR
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // NR NSA, restricted
+        doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // NR NSA, not restricted
+        doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // NR NSA, sub 6 frequency
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // NR NSA, millimeter wave frequency
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testUpdateOverrideNetworkTypeLte() throws Exception {
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+
+        // normal LTE
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // LTE CA
+        doReturn(true).when(mServiceState).isUsingCarrierAggregation();
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // LTE ADVANCED PRO
+        doReturn("test_patternShowAdvanced").when(mServiceState).getOperatorAlphaLongRaw();
+        mBundle.putString(CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING,
+                ".*_patternShowAdvanced");
+        broadcastCarrierConfigs();
+        updateOverrideNetworkType();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateLegacy() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_HSPAP).when(mServiceState).getDataNetworkType();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("legacy", getCurrentState().getName());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateRestricted() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("restricted", getCurrentState().getName());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateIdle() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
+        doReturn(PhoneInternalInterface.DataActivityState.DORMANT)
+                .when(mPhone).getDataActivityState();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateLteConnected() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
+        doReturn(PhoneInternalInterface.DataActivityState.DATAINANDOUT)
+                .when(mPhone).getDataActivityState();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("not_restricted_rrc_con", getCurrentState().getName());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateNrConnected() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("connected", getCurrentState().getName());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateNrConnectedMmwave() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("connected_mmwave", getCurrentState().getName());
+    }
+
+    @Test
+    public void testEventDataRatChanged() throws Exception {
+        testTransitionToCurrentStateLegacy();
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+
+        mNetworkTypeController.sendMessage(EVENT_DATA_RAT_CHANGED);
+        processAllMessages();
+        assertEquals("connected", getCurrentState().getName());
+    }
+
+    @Test
+    public void testEventNrStateChanged() throws Exception {
+        testTransitionToCurrentStateNrConnected();
+        doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
+
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        processAllMessages();
+        assertEquals("restricted", getCurrentState().getName());
+    }
+
+    @Test
+    public void testEventNrFrequencyRangeChanged() throws Exception {
+        testTransitionToCurrentStateNrConnectedMmwave();
+        doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
+
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        processAllMessages();
+        assertEquals("connected", getCurrentState().getName());
+    }
+
+    @Test
+    public void testEventDataActivityChanged() throws Exception {
+        testTransitionToCurrentStateLteConnected();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        doReturn(PhoneInternalInterface.DataActivityState.DORMANT)
+                .when(mPhone).getDataActivityState();
+
+        mNetworkTypeController.sendMessage(EVENT_DATA_ACTIVITY_CHANGED);
+        processAllMessages();
+        assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
+    }
+
+    @Test
+    public void testEventPhysicalChannelConfigNotifChanged() throws Exception {
+        testTransitionToCurrentStateNrConnected();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED,
+                new AsyncResult(null, false, null));
+        processAllMessages();
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testPrimaryTimerExpire() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testPrimaryTimerReset() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // trigger 10 second timer after disconnecting from NR
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // reconnect to NR in the middle of the timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+
+        // timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // timer should not have gone off
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testPrimaryTimerExpireMmwave() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second timer
+        doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        processAllMessages();
+
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testPrimaryTimerResetMmwave() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // trigger 10 second timer after disconnecting from NR
+        doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // reconnect to NR in the middle of the timer
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+
+        // timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // timer should not have gone off
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testSecondaryTimerExpire() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected,any,30");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second primary timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // should trigger 30 second secondary timer
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // secondary timer expires
+        moveTimeForward(30 * 1000);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testSecondaryTimerReset() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected,any,30");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second primary timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        processAllMessages();
+
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // should trigger 30 second secondary timer
+        assertEquals("legacy", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // reconnect to NR in the middle of the timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+
+        // secondary timer expires
+        moveTimeForward(30 * 1000);
+        processAllMessages();
+
+        // timer should not have gone off
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testSecondaryTimerExpireMmwave() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,30");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second primary timer
+        doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // should trigger 30 second secondary timer
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // secondary timer expires
+        moveTimeForward(30 * 1000);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
+    public void testSecondaryTimerResetMmwave() throws Exception {
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,30");
+        broadcastCarrierConfigs();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // should trigger 10 second primary timer
+        doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // primary timer expires
+        moveTimeForward(10 * 1000);
+        processAllMessages();
+
+        // should trigger 30 second secondary timer
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // reconnect to NR in the middle of the timer
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+
+        // secondary timer expires
+        moveTimeForward(30 * 1000);
+        processAllMessages();
+
+        // timer should not have gone off
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 3abec86..87804b0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -174,6 +174,8 @@
     @Mock
     protected DcTracker mDcTracker;
     @Mock
+    protected DisplayInfoController mDisplayInfoController;
+    @Mock
     protected GsmCdmaCall mGsmCdmaCall;
     @Mock
     protected ImsCall mImsCall;
@@ -446,6 +448,8 @@
                 .makeIccPhoneBookInterfaceManager(nullable(Phone.class));
         doReturn(mDcTracker).when(mTelephonyComponentFactory)
                 .makeDcTracker(nullable(Phone.class), anyInt());
+        doReturn(mDisplayInfoController).when(mTelephonyComponentFactory)
+                .makeDisplayInfoController(nullable(Phone.class));
         doReturn(mWspTypeDecoder).when(mTelephonyComponentFactory)
                 .makeWspTypeDecoder(nullable(byte[].class));
         doReturn(mImsCT).when(mTelephonyComponentFactory)
@@ -490,6 +494,8 @@
         doReturn(PhoneConstants.PHONE_TYPE_GSM).when(mPhone).getPhoneType();
         doReturn(mCT).when(mPhone).getCallTracker();
         doReturn(mSST).when(mPhone).getServiceStateTracker();
+        doReturn(mDeviceStateMonitor).when(mPhone).getDeviceStateMonitor();
+        doReturn(mDisplayInfoController).when(mPhone).getDisplayInfoController();
         doReturn(mEmergencyNumberTracker).when(mPhone).getEmergencyNumberTracker();
         doReturn(mCarrierSignalAgent).when(mPhone).getCarrierSignalAgent();
         doReturn(mCarrierActionAgent).when(mPhone).getCarrierActionAgent();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
index 110098a..89cfd85 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DcTrackerTest.java
@@ -1705,12 +1705,6 @@
         waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
     }
 
-    private boolean getHysteresisStatus() throws Exception {
-        Field field = DcTracker.class.getDeclaredField(("mHysteresis"));
-        field.setAccessible(true);
-        return (boolean) field.get(mDct);
-    }
-
     private boolean getWatchdogStatus() throws Exception {
         Field field = DcTracker.class.getDeclaredField(("mWatchdog"));
         field.setAccessible(true);
@@ -1757,11 +1751,13 @@
         int id = setUpDataConnection();
         setUpSubscriptionPlans(false);
         setUpWatchdogTimer();
-        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA))
+                .when(mDisplayInfoController).getTelephonyDisplayInfo();
         doReturn(1).when(mPhone).getSubId();
 
         // NetCapability should be metered when connected to 5G with no unmetered plan or frequency
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
         waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         verify(mDataConnection, times(1)).onMeterednessChanged(false);
 
@@ -1774,14 +1770,18 @@
         waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
         // NetCapability should switch to unmetered when fr=MMWAVE and MMWAVE unmetered
-        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE))
+                .when(mDisplayInfoController).getTelephonyDisplayInfo();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
         waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         verify(mDataConnection, times(1)).onMeterednessChanged(true);
 
         // NetCapability should switch to metered when fr=SUB6 and MMWAVE unmetered
-        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA))
+                .when(mDisplayInfoController).getTelephonyDisplayInfo();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
         waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         verify(mDataConnection, times(2)).onMeterednessChanged(false);
 
@@ -1795,7 +1795,7 @@
         waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
 
         // NetCapability should switch to unmetered when fr=SUB6 and SUB6 unmetered
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
         waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         verify(mDataConnection, times(2)).onMeterednessChanged(true);
 
@@ -1810,14 +1810,18 @@
         setUpWatchdogTimer();
 
         // NetCapability should be unmetered when connected to 5G
-        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA))
+                .when(mDisplayInfoController).getTelephonyDisplayInfo();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
         waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         verify(mDataConnection, times(1)).onMeterednessChanged(true);
 
         // NetCapability should be metered when disconnected from 5G
-        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE))
+                .when(mDisplayInfoController).getTelephonyDisplayInfo();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
         waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         verify(mDataConnection, times(1)).onMeterednessChanged(false);
 
@@ -1825,221 +1829,37 @@
     }
 
     @Test
-    public void testReevaluateUnmeteredConnectionsOnHysteresis() throws Exception {
-        initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
-        int id = setUpDataConnection();
-        setUpSubscriptionPlans(true);
-        setUpWatchdogTimer();
-
-        // Hysteresis active for 100s
-        doReturn(1).when(mPhone).getSubId();
-        mBundle.putInt(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, 100);
-        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId());
-        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
-        mContext.sendBroadcast(intent);
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-
-        // Hysteresis inactive when unmetered and never connected to 5G
-        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-        assertFalse(getHysteresisStatus());
-
-        // Hysteresis inactive when unmetered and connected to 5G
-        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-        assertFalse(getHysteresisStatus());
-
-        // Hysteresis active when unmetered and disconnected after connected to 5G
-        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-        assertTrue(getHysteresisStatus());
-
-        // NetCapability metered when hysteresis timer goes off
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_5G_TIMER_HYSTERESIS));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-        assertFalse(getHysteresisStatus());
-        verify(mDataConnection, times(1)).onMeterednessChanged(true);
-
-        // Hysteresis inactive when reconnected after timer goes off
-        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-        assertFalse(getHysteresisStatus());
-
-        // Hysteresis disabled
-        doReturn(2).when(mPhone).getSubId();
-        mBundle.putInt(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, 0);
-        intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId());
-        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
-        mContext.sendBroadcast(intent);
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-
-        // Hysteresis inactive when CarrierConfig is set to 0
-        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-        assertFalse(getHysteresisStatus());
-
-        resetDataConnection(id);
-    }
-
-    @Test
     public void testReevaluateUnmeteredConnectionsOnWatchdog() throws Exception {
         initApns(PhoneConstants.APN_TYPE_DEFAULT, new String[]{PhoneConstants.APN_TYPE_ALL});
         int id = setUpDataConnection();
         setUpSubscriptionPlans(true);
         setUpWatchdogTimer();
 
-        // Watchdog inactive when unmetered and never connected to 5G
-        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_5G_TIMER_WATCHDOG));
+        // Watchdog inactive when unmetered and not connected to 5G
+        doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE))
+                .when(mDisplayInfoController).getTelephonyDisplayInfo();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_NR_TIMER_WATCHDOG));
         waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         assertFalse(getWatchdogStatus());
 
-        // Hysteresis active for 100s
-        doReturn(1).when(mPhone).getSubId();
-        mBundle.putInt(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, 100);
-        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId());
-        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
-        mContext.sendBroadcast(intent);
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-
         // Watchdog active when unmetered and connected to 5G
-        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA))
+                .when(mDisplayInfoController).getTelephonyDisplayInfo();
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
         waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         assertTrue(getWatchdogStatus());
-        assertFalse(getHysteresisStatus());
-
-        // Watchdog active during hysteresis
-        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-        assertTrue(getHysteresisStatus());
-        assertTrue(getWatchdogStatus());
 
         // Watchdog inactive when metered
         setUpSubscriptionPlans(false);
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
+        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED));
         waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
         assertFalse(getWatchdogStatus());
 
         resetDataConnection(id);
     }
 
-    @Test
-    public void testGetNrDisplayType() {
-        ArgumentCaptor<TelephonyDisplayInfo> captor =
-                ArgumentCaptor.forClass(TelephonyDisplayInfo.class);
-        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
-
-        // set up 5G icon configuration
-        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING,
-                "connected_mmwave:5G_Plus,connected:5G,not_restricted_rrc_idle:5G,"
-                        + "not_restricted_rrc_con:5G");
-        doReturn(1).when(mPhone).getSubId();
-        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId());
-        intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
-        mContext.sendBroadcast(intent);
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-
-        // not NR
-        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-
-        verify(mPhone, times(1)).notifyDisplayInfoChanged(captor.capture());
-        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
-                captor.getValue().getOverrideNetworkType());
-
-        // NR NSA, restricted
-        doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-
-        verify(mPhone, times(2)).notifyDisplayInfoChanged(captor.capture());
-        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
-                captor.getValue().getOverrideNetworkType());
-
-        // NR NSA, not restricted
-        doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-
-        verify(mPhone, times(3)).notifyDisplayInfoChanged(captor.capture());
-        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
-                captor.getValue().getOverrideNetworkType());
-
-        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
-
-        // NR NSA, sub 6 frequency
-        doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(mServiceState).getNrFrequencyRange();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-
-        verify(mPhone, times(4)).notifyDisplayInfoChanged(captor.capture());
-        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
-                captor.getValue().getOverrideNetworkType());
-
-        // NR NSA, millimeter wave frequency
-        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-
-        verify(mPhone, times(5)).notifyDisplayInfoChanged(captor.capture());
-        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
-                captor.getValue().getOverrideNetworkType());
-    }
-
-    @Test
-    public void testGetLteDisplayType() {
-        ArgumentCaptor<TelephonyDisplayInfo> captor =
-                ArgumentCaptor.forClass(TelephonyDisplayInfo.class);
-
-        // normal LTE
-        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-
-        verify(mPhone, times(1)).notifyDisplayInfoChanged(captor.capture());
-        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
-                captor.getValue().getOverrideNetworkType());
-
-        // LTE CA
-        doReturn(TelephonyManager.NETWORK_TYPE_LTE_CA).when(mServiceState).getDataNetworkType();
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-
-        verify(mPhone, times(2)).notifyDisplayInfoChanged(captor.capture());
-        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
-                captor.getValue().getOverrideNetworkType());
-
-        // TODO: LTE ADVANCED PRO
-    }
-
-    @Test
-    public void testUpdateDisplayInfo() {
-        ArgumentCaptor<TelephonyDisplayInfo> captor =
-                ArgumentCaptor.forClass(TelephonyDisplayInfo.class);
-        doReturn(TelephonyManager.NETWORK_TYPE_HSPAP).when(mServiceState).getDataNetworkType();
-
-        mDct.sendMessage(mDct.obtainMessage(DctConstants.EVENT_SERVICE_STATE_CHANGED));
-        waitForLastHandlerAction(mDcTrackerTestHandler.getThreadHandler());
-
-        verify(mPhone, times(1)).notifyDisplayInfoChanged(captor.capture());
-        TelephonyDisplayInfo telephonyDisplayInfo = captor.getValue();
-        assertEquals(TelephonyManager.NETWORK_TYPE_HSPAP, telephonyDisplayInfo.getNetworkType());
-        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
-                telephonyDisplayInfo.getOverrideNetworkType());
-    }
-
     /**
      * Test if this is a path prefix match against the given Uri. Verifies that
      * scheme, authority, and atomic path segments match.