Merge "Always call unbindService no matter what bindService returns"
diff --git a/src/java/com/android/internal/telephony/NetworkIndication.java b/src/java/com/android/internal/telephony/NetworkIndication.java
index 3800563..b8c8f53 100644
--- a/src/java/com/android/internal/telephony/NetworkIndication.java
+++ b/src/java/com/android/internal/telephony/NetworkIndication.java
@@ -29,6 +29,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SUPP_SVC_NOTIFICATION;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_VOICE_RADIO_TECH_CHANGED;
 
+import android.annotation.ElapsedRealtimeLong;
 import android.hardware.radio.network.IRadioNetworkIndication;
 import android.os.AsyncResult;
 import android.sysprop.TelephonyProperties;
@@ -237,17 +238,35 @@
      * Indicates when radio has received a NITZ time message.
      * @param indicationType Type of radio indication
      * @param nitzTime NITZ time string in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt"
-     * @param receivedTime milliseconds since boot that the NITZ time was received
+     * @param receivedTimeMs time according to {@link android.os.SystemClock#elapsedRealtime()} when
+     *        the RIL sent the NITZ time to the framework
+     * @param ageMs time in milliseconds indicating how long NITZ was cached in RIL and modem.
+     *        This must track true age and therefore must be calculated using clocks that
+     *        include the time spend in sleep / low power states. If it can not be guaranteed,
+     *        there must not be any caching done at the modem and should fill in 0 for ageMs
      */
-    public void nitzTimeReceived(int indicationType, String nitzTime, long receivedTime) {
+    public void nitzTimeReceived(int indicationType, String nitzTime,
+        @ElapsedRealtimeLong long receivedTimeMs, long ageMs) {
         mRil.processIndication(indicationType);
 
         if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NITZ_TIME_RECEIVED, nitzTime);
 
+        // Ignore the NITZ if ageMs is not a valid time, e.g. negative or greater than
+        // receivedTimeMs.
+        if ((ageMs < 0) || (ageMs >= receivedTimeMs)) {
+            AnomalyReporter.reportAnomaly(UUID.fromString("fc7c56d4-485d-475a-aaff-394203c6cdfc"),
+                    "NITZ indication with invalid age");
+
+            mRil.riljLoge("age time is invalid, ignoring nitzTimeReceived indication. "
+                + "receivedTimeMs = " + receivedTimeMs + ", ageMs = " + ageMs);
+            return;
+        }
+
         // TODO: Clean this up with a parcelable class for better self-documentation
-        Object[] result = new Object[2];
+        Object[] result = new Object[3];
         result[0] = nitzTime;
-        result[1] = receivedTime;
+        result[1] = receivedTimeMs;
+        result[2] = ageMs;
 
         boolean ignoreNitz = TelephonyProperties.ignore_nitz().orElse(false);
 
diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java
index a1724a6..d4a2a50 100644
--- a/src/java/com/android/internal/telephony/NetworkTypeController.java
+++ b/src/java/com/android/internal/telephony/NetworkTypeController.java
@@ -98,6 +98,7 @@
     private static final int EVENT_INITIALIZE = 12;
     private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 13;
     private static final int EVENT_PCO_DATA_CHANGED = 14;
+    private static final int EVENT_BANDWIDTH_CHANGED = 15;
 
     private static final String[] sEvents = new String[EVENT_PCO_DATA_CHANGED + 1];
     static {
@@ -142,6 +143,7 @@
     private boolean mIsSecondaryTimerActive;
     private boolean mIsTimerResetEnabledForLegacyStateRRCIdle;
     private int mLtePlusThresholdBandwidth;
+    private int mNrAdvancedThresholdBandwidth;
     private int[] mAdditionalNrAdvancedBandsList;
     private String mPrimaryTimerState;
     private String mSecondaryTimerState;
@@ -151,6 +153,7 @@
     private Boolean mIsNrAdvancedAllowedByPco = false;
     private int mNrAdvancedCapablePcoId = 0;
     private boolean mIsUsingUserDataForRrcDetection = false;
+    private boolean mEnableNrAdvancedWhileRoaming = true;
 
     /**
      * NetworkTypeController constructor.
@@ -200,6 +203,8 @@
         mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getHandler(),
                 EVENT_DATA_RAT_CHANGED, null);
+        mPhone.getServiceStateTracker().registerForBandwidthChanged(
+                getHandler(), EVENT_BANDWIDTH_CHANGED, null);
         mIsPhysicalChannelConfig16Supported = mPhone.getContext().getSystemService(
                 TelephonyManager.class).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
@@ -246,6 +251,10 @@
                         CarrierConfigManager.KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL);
         mLtePlusThresholdBandwidth = CarrierConfigManager.getDefaultConfig().getInt(
                 CarrierConfigManager.KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT);
+        mNrAdvancedThresholdBandwidth = CarrierConfigManager.getDefaultConfig().getInt(
+                CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT);
+        mEnableNrAdvancedWhileRoaming = CarrierConfigManager.getDefaultConfig().getBoolean(
+                CarrierConfigManager.KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL);
 
         CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
                 .getSystemService(Context.CARRIER_CONFIG_SERVICE);
@@ -276,10 +285,15 @@
                 mLtePlusThresholdBandwidth = b.getInt(
                         CarrierConfigManager.KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT,
                         mLtePlusThresholdBandwidth);
+                mNrAdvancedThresholdBandwidth = b.getInt(
+                        CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT,
+                        mNrAdvancedThresholdBandwidth);
                 mAdditionalNrAdvancedBandsList = b.getIntArray(
                         CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY);
                 mNrAdvancedCapablePcoId = b.getInt(
                         CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT);
+                mEnableNrAdvancedWhileRoaming = b.getBoolean(
+                        CarrierConfigManager.KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL);
                 mIsUsingUserDataForRrcDetection = b.getBoolean(
                         CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL);
                 if (mIsPhysicalChannelConfig16Supported && mIsUsingUserDataForRrcDetection) {
@@ -508,6 +522,7 @@
                 case EVENT_NR_STATE_CHANGED:
                 case EVENT_NR_FREQUENCY_CHANGED:
                 case EVENT_PCO_DATA_CHANGED:
+                case EVENT_BANDWIDTH_CHANGED:
                     // ignored
                     break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
@@ -877,6 +892,9 @@
                         sendMessage(EVENT_NR_STATE_CHANGED);
                     }
                     break;
+                case EVENT_BANDWIDTH_CHANGED:
+                    updateNrAdvancedState();
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -1133,8 +1151,32 @@
                 == NetworkRegistrationInfo.NR_STATE_RESTRICTED;
     }
 
+    /**
+     * @return {@code true} if the device is in NR advanced mode (i.e. 5G+).
+     */
     private boolean isNrAdvanced() {
-        return isNrAdvancedCapable() && (isNrMmwave() || isAdditionalNrAdvancedBand());
+        // Check PCO requirement. For carriers using PCO to indicate whether the data connection is
+        // NR advanced capable, mNrAdvancedCapablePcoId should be configured to non-zero.
+        if (mNrAdvancedCapablePcoId > 0 && !mIsNrAdvancedAllowedByPco) {
+            return false;
+        }
+
+        // Check if NR advanced is enabled when the device is roaming. Some carriers disable it
+        // while the device is roaming.
+        if (mPhone.getServiceState().getDataRoaming() && !mEnableNrAdvancedWhileRoaming) {
+            return false;
+        }
+
+        // Check if meeting minimum bandwidth requirement. For most carriers, there is no minimum
+        // bandwidth requirement and mNrAdvancedThresholdBandwidth is 0.
+        if (IntStream.of(mPhone.getServiceState().getCellBandwidths()).sum()
+                < mNrAdvancedThresholdBandwidth) {
+            return false;
+        }
+
+        // If all above tests passed, then check if the device is using millimeter wave bands or
+        // carrier designated bands.
+        return isNrMmwave() || isAdditionalNrAdvancedBand();
     }
 
     private boolean isNrMmwave() {
@@ -1158,13 +1200,6 @@
         return false;
     }
 
-    private boolean isNrAdvancedCapable() {
-        if (mNrAdvancedCapablePcoId > 0) {
-            return mIsNrAdvancedAllowedByPco;
-        }
-        return true;
-    }
-
     private boolean isLte(int rat) {
         return rat == TelephonyManager.NETWORK_TYPE_LTE
                 || rat == TelephonyManager.NETWORK_TYPE_LTE_CA;
diff --git a/src/java/com/android/internal/telephony/NitzSignal.java b/src/java/com/android/internal/telephony/NitzSignal.java
new file mode 100644
index 0000000..889fe95
--- /dev/null
+++ b/src/java/com/android/internal/telephony/NitzSignal.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2021 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.annotation.DurationMillisLong;
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
+import android.os.TimestampedValue;
+
+import java.time.Duration;
+import java.util.Objects;
+
+/** NITZ information and associated metadata. */
+public final class NitzSignal {
+
+    @ElapsedRealtimeLong private final long mReceiptElapsedMillis;
+    @NonNull private final NitzData mNitzData;
+    @DurationMillisLong private final long mAgeMillis;
+
+    /**
+     * @param receiptElapsedMillis the time according to {@link
+     *     android.os.SystemClock#elapsedRealtime()} when the NITZ signal was first received by
+     *     the platform code
+     * @param nitzData the NITZ data
+     * @param ageMillis the age of the NITZ when it was passed to the platform, e.g. if it was
+     *     cached by the modem for a period of time. Must not be negative.
+     */
+    public NitzSignal(
+            @ElapsedRealtimeLong long receiptElapsedMillis,
+            @NonNull NitzData nitzData,
+            long ageMillis) {
+        mReceiptElapsedMillis = receiptElapsedMillis;
+        mNitzData = Objects.requireNonNull(nitzData);
+        if (ageMillis < 0) {
+            throw new IllegalArgumentException("ageMillis < 0");
+        }
+        mAgeMillis = ageMillis;
+    }
+
+    /**
+     * Returns the time according to {@link android.os.SystemClock#elapsedRealtime()} when the NITZ
+     * signal was first received by the platform code.
+     */
+    @ElapsedRealtimeLong
+    public long getReceiptElapsedRealtimeMillis() {
+        return mReceiptElapsedMillis;
+    }
+
+    /**
+     * Returns the NITZ data.
+     */
+    @NonNull
+    public NitzData getNitzData() {
+        return mNitzData;
+    }
+
+    /**
+     * Returns the age of the NITZ when it was passed to the platform, e.g. if it was cached by the
+     * modem for a period of time. Must not be negative.
+     */
+    @DurationMillisLong
+    public long getAgeMillis() {
+        return mAgeMillis;
+    }
+
+    /**
+     * Returns a derived property of {@code receiptElapsedMillis - ageMillis}, i.e. the time
+     * according to the elapsed realtime clock when the NITZ signal was actually received by this
+     * device taking into time it was cached by layers before the RIL.
+     */
+    @ElapsedRealtimeLong
+    public long getAgeAdjustedElapsedRealtimeMillis() {
+        return mReceiptElapsedMillis - mAgeMillis;
+    }
+
+    /**
+     * Creates a {@link android.os.TimestampedValue} containing the UTC time as the number of
+     * milliseconds since the start of the Unix epoch. The reference time is the time according to
+     * the elapsed realtime clock when that would have been the time, accounting for receipt time
+     * and age.
+     */
+    public TimestampedValue<Long> createTimeSignal() {
+        return new TimestampedValue<>(
+                getAgeAdjustedElapsedRealtimeMillis(),
+                getNitzData().getCurrentTimeInMillis());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        NitzSignal that = (NitzSignal) o;
+        return mReceiptElapsedMillis == that.mReceiptElapsedMillis
+                && mAgeMillis == that.mAgeMillis
+                && mNitzData.equals(that.mNitzData);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mReceiptElapsedMillis, mNitzData, mAgeMillis);
+    }
+
+    @Override
+    public String toString() {
+        return "NitzSignal{"
+                + "mReceiptElapsedMillis=" + Duration.ofMillis(mReceiptElapsedMillis)
+                + ", mNitzData=" + mNitzData
+                + ", mAgeMillis=" + mAgeMillis
+                + '}';
+    }
+}
diff --git a/src/java/com/android/internal/telephony/NitzStateMachine.java b/src/java/com/android/internal/telephony/NitzStateMachine.java
index e1f854c..f4f4deb 100644
--- a/src/java/com/android/internal/telephony/NitzStateMachine.java
+++ b/src/java/com/android/internal/telephony/NitzStateMachine.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.os.TimestampedValue;
 import android.provider.Settings;
 
 import com.android.internal.util.IndentingPrintWriter;
@@ -65,7 +64,7 @@
     /**
      * Handle a new NITZ signal being received.
      */
-    void handleNitzReceived(@NonNull TimestampedValue<NitzData> nitzSignal);
+    void handleNitzReceived(@NonNull NitzSignal nitzSignal);
 
     /**
      * Handle the user putting the device into or out of airplane mode
@@ -90,14 +89,14 @@
     interface DeviceState {
 
         /**
-         * If time between NITZ updates is less than {@link #getNitzUpdateSpacingMillis()} the
-         * update may be ignored.
+         * If elapsed time between two NITZ signals is less than this value then the second signal
+         * can be ignored.
          */
         int getNitzUpdateSpacingMillis();
 
         /**
-         * If {@link #getNitzUpdateSpacingMillis()} hasn't been exceeded but update is >
-         * {@link #getNitzUpdateDiffMillis()} do the update
+         * If UTC time between two NITZ signals is less than this value then the second signal can
+         * be ignored.
          */
         int getNitzUpdateDiffMillis();
 
@@ -109,7 +108,7 @@
         /**
          * Returns the same value as {@link SystemClock#elapsedRealtime()}.
          */
-        long elapsedRealtime();
+        long elapsedRealtimeMillis();
 
         /**
          * Returns the same value as {@link System#currentTimeMillis()}.
@@ -158,7 +157,7 @@
         }
 
         @Override
-        public long elapsedRealtime() {
+        public long elapsedRealtimeMillis() {
             return SystemClock.elapsedRealtime();
         }
 
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 86b9786..9d16f1f 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -2198,11 +2198,11 @@
      * @return Current signal strength as SignalStrength
      */
     public SignalStrength getSignalStrength() {
-        ServiceStateTracker sst = getServiceStateTracker();
-        if (sst == null) {
+        SignalStrengthController ssc = getSignalStrengthController();
+        if (ssc == null) {
             return new SignalStrength();
         } else {
-            return sst.getSignalStrength();
+            return ssc.getSignalStrength();
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 7c47293..62ec70e 100755
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -49,7 +49,6 @@
 import android.os.RegistrantList;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.os.TimestampedValue;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.preference.PreferenceManager;
@@ -73,7 +72,6 @@
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
 import android.telephony.ServiceState.RilRadioTechnology;
-import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
@@ -118,7 +116,6 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
@@ -126,10 +123,8 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
 import java.util.stream.Collectors;
 
 /**
@@ -142,9 +137,6 @@
 
     private static final String PROP_FORCE_ROAMING = "telephony.test.forceRoaming";
 
-    private static final long SIGNAL_STRENGTH_REFRESH_THRESHOLD_IN_MS =
-            TimeUnit.SECONDS.toMillis(10);
-
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private CommandsInterface mCi;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -186,10 +178,6 @@
 
     private final Set<Integer> mRadioPowerOffReasons = new HashSet();
 
-    @UnsupportedAppUsage
-    private SignalStrength mSignalStrength;
-    private long mSignalStrengthUpdatedTime;
-
     // TODO - this should not be public, right now used externally GsmConnetion.
     public RestrictedState mRestrictedState;
 
@@ -203,13 +191,6 @@
     @UnsupportedAppUsage
     private boolean mDesiredPowerState;
 
-    /**
-     * By default, strength polling is enabled.  However, if we're
-     * getting unsolicited signal strength updates from the radio, set
-     * value to true and don't bother polling any more.
-     */
-    private boolean mDontPollSignalStrength = false;
-
     @UnsupportedAppUsage
     private RegistrantList mVoiceRoamingOnRegistrants = new RegistrantList();
     @UnsupportedAppUsage
@@ -231,6 +212,7 @@
     private RegistrantList mNrStateChangedRegistrants = new RegistrantList();
     private RegistrantList mNrFrequencyChangedRegistrants = new RegistrantList();
     private RegistrantList mCssIndicatorChangedRegistrants = new RegistrantList();
+    private final RegistrantList mBandwidthChangedRegistrants = new RegistrantList();
     private final RegistrantList mAirplaneModeChangedRegistrants = new RegistrantList();
     private final RegistrantList mAreaCodeChangedRegistrants = new RegistrantList();
 
@@ -238,23 +220,17 @@
     private boolean mPendingRadioPowerOffAfterDataOff = false;
     private int mPendingRadioPowerOffAfterDataOffTag = 0;
 
-    /** Signal strength poll rate. */
-    private static final int POLL_PERIOD_MILLIS = 20 * 1000;
-
     /** Waiting period before recheck gprs and voice registration. */
     public static final int DEFAULT_GPRS_CHECK_PERIOD_MILLIS = 60 * 1000;
 
     /** GSM events */
     protected static final int EVENT_RADIO_STATE_CHANGED                    = 1;
     protected static final int EVENT_NETWORK_STATE_CHANGED                  = 2;
-    protected static final int EVENT_GET_SIGNAL_STRENGTH                    = 3;
     protected static final int EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION    = 4;
     protected static final int EVENT_POLL_STATE_PS_CELLULAR_REGISTRATION    = 5;
     protected static final int EVENT_POLL_STATE_PS_IWLAN_REGISTRATION       = 6;
     protected static final int EVENT_POLL_STATE_OPERATOR                    = 7;
-    protected static final int EVENT_POLL_SIGNAL_STRENGTH                   = 10;
     protected static final int EVENT_NITZ_TIME                              = 11;
-    protected static final int EVENT_SIGNAL_STRENGTH_UPDATE                 = 12;
     protected static final int EVENT_POLL_STATE_NETWORK_SELECTION_MODE      = 14;
     protected static final int EVENT_GET_LOC_DONE                           = 15;
     protected static final int EVENT_SIM_RECORDS_LOADED                     = 16;
@@ -623,22 +599,6 @@
     private final TransportManager mTransportManager;
     private final SparseArray<NetworkRegistrationManager> mRegStateManagers = new SparseArray<>();
 
-    /* list of LTE EARFCNs (E-UTRA Absolute Radio Frequency Channel Number,
-     * Reference: 3GPP TS 36.104 5.4.3)
-     * inclusive ranges for which the lte rsrp boost is applied */
-    private ArrayList<Pair<Integer, Integer>> mEarfcnPairListForRsrpBoost = null;
-    private int mLteRsrpBoost = 0; // offset which is reduced from the rsrp threshold
-                                   // while calculating signal strength level.
-
-    /* Ranges of NR ARFCNs (5G Absolute Radio Frequency Channel Number,
-     * Reference: 3GPP TS 38.104)
-     * inclusive ranges for which the corresponding nr rsrp boost is applied */
-    private ArrayList<Pair<Integer, Integer>> mNrarfcnRangeListForRsrpBoost = null;
-    private int[] mNrRsrpBoost;
-
-    private final Object mRsrpBoostLock = new Object();
-    private static final int INVALID_ARFCN = -1;
-
     /* Last known TAC/LAC */
     private int mLastKnownAreaCode = CellInfo.UNAVAILABLE;
 
@@ -671,7 +631,6 @@
         mOutOfServiceSS.setStateOutOfService();
 
         mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
-        mCi.setOnSignalStrengthUpdate(this, EVENT_SIGNAL_STRENGTH_UPDATE, null);
         mCi.registerForCellInfoList(this, EVENT_UNSOL_CELL_INFO_LIST, null);
         mCi.registerForPhysicalChannelConfiguration(this, EVENT_PHYSICAL_CHANNEL_CONFIG, null);
 
@@ -710,7 +669,6 @@
                 enableCellularOnBoot);
 
 
-        setSignalStrengthDefaultValues();
         mPhone.getCarrierActionAgent().registerForCarrierAction(CARRIER_ACTION_SET_RADIO_ENABLED,
                 this, EVENT_RADIO_POWER_FROM_CARRIER, null, false);
 
@@ -779,7 +737,6 @@
         mNewSS.setStateOutOfService();
         mLastCellInfoReqTime = 0;
         mLastCellInfoList = null;
-        mSignalStrength = new SignalStrength();
         mStartedGprsRegCheck = false;
         mReportedGprsNoReg = false;
         mMdn = null;
@@ -789,7 +746,7 @@
         mLastNitzData = null;
         mNitzState.handleNetworkUnavailable();
         mCellIdentity = null;
-        mSignalStrengthUpdatedTime = System.currentTimeMillis();
+        mPhone.getSignalStrengthController().setSignalStrengthDefaultValues();
 
         //cancel any pending pollstate request on voice tech switching
         cancelPollState();
@@ -828,7 +785,7 @@
         // switching between GSM and CDMA phone), because the unsolicited signal strength
         // information might come late or even never come. This will get the accurate signal
         // strength information displayed on the UI.
-        mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
+        mPhone.getSignalStrengthController().getSignalStrengthFromCi();
         sendMessage(obtainMessage(EVENT_PHONE_TYPE_SWITCHED));
 
         logPhoneTypeChange();
@@ -859,7 +816,7 @@
     }
 
     public void dispose() {
-        mCi.unSetOnSignalStrengthUpdate(this);
+        mPhone.getSignalStrengthController().dispose();
         mUiccController.unregisterForIccChanged(this);
         mCi.unregisterForCellInfoList(this);
         mCi.unregisterForPhysicalChannelConfiguration(this);
@@ -885,23 +842,6 @@
         return mLastPhysicalChannelConfigList;
     }
 
-    private SignalStrength mLastSignalStrength = null;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    protected boolean notifySignalStrength() {
-        boolean notified = false;
-        if (!mSignalStrength.equals(mLastSignalStrength)) {
-            try {
-                mPhone.notifySignalStrength();
-                notified = true;
-                mLastSignalStrength = mSignalStrength;
-            } catch (NullPointerException ex) {
-                loge("updateSignalStrength() Phone already destroyed: " + ex
-                        + "SignalStrength not notified");
-            }
-        }
-        return notified;
-    }
-
     /**
      * Notify all mVoiceRegStateOrRatChangedRegistrants using an
      * AsyncResult in msg.obj where AsyncResult#result contains the
@@ -1367,20 +1307,6 @@
                 pollStateInternal(true);
                 break;
 
-            case EVENT_GET_SIGNAL_STRENGTH:
-                // This callback is called when signal strength is polled
-                // all by itself
-
-                if (!(mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON)) {
-                    // Polling will continue when radio turns back on
-                    return;
-                }
-                ar = (AsyncResult) msg.obj;
-                onSignalStrengthResult(ar);
-                queueNextSignalStrengthPoll();
-
-                break;
-
             case EVENT_GET_LOC_DONE:
                 ar = (AsyncResult) msg.obj;
                 if (ar.exception == null) {
@@ -1421,32 +1347,20 @@
                 }
                 break;
 
-            case EVENT_POLL_SIGNAL_STRENGTH:
-                // Just poll signal strength...not part of pollState()
-
-                mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
-                break;
-
-            case EVENT_NITZ_TIME:
+            case EVENT_NITZ_TIME: {
                 ar = (AsyncResult) msg.obj;
 
-                String nitzString = (String)((Object[])ar.result)[0];
-                long nitzReceiveTime = ((Long)((Object[])ar.result)[1]).longValue();
+                Object[] nitzArgs = (Object[])ar.result;
+                String nitzString = (String)nitzArgs[0];
+                long nitzReceiveTimeMs = ((Long)nitzArgs[1]).longValue();
+                long ageMs = 0;
+                if (nitzArgs.length >= 3) {
+                    ageMs = ((Long)nitzArgs[2]).longValue();
+                }
 
-                setTimeFromNITZString(nitzString, nitzReceiveTime);
+                setTimeFromNITZString(nitzString, nitzReceiveTimeMs, ageMs);
                 break;
-
-            case EVENT_SIGNAL_STRENGTH_UPDATE:
-                // This is a notification from CommandsInterface.setOnSignalStrengthUpdate
-
-                ar = (AsyncResult) msg.obj;
-
-                // The radio is telling us about signal strength changes
-                // we don't have to ask it
-                mDontPollSignalStrength = true;
-
-                onSignalStrengthResult(ar);
-                break;
+            }
 
             case EVENT_SIM_RECORDS_LOADED:
                 log("EVENT_SIM_RECORDS_LOADED: what=" + msg.what);
@@ -2343,7 +2257,7 @@
                             && ServiceState.isPsOnlyTech(newDataRat))
                             || (ServiceState.isPsOnlyTech(oldDataRAT)
                             && ServiceState.isCdma(newDataRat))) {
-                        mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
+                        mPhone.getSignalStrengthController().getSignalStrengthFromCi();
                     }
 
                     // voice roaming state in done while handling EVENT_POLL_STATE_REGISTRATION_CDMA
@@ -2354,7 +2268,8 @@
                     mNewSS.setDataRoamingFromRegistration(isDataRoaming);
                 }
 
-                updateServiceStateArfcnRsrpBoost(mNewSS, networkRegState.getCellIdentity());
+                mPhone.getSignalStrengthController().updateServiceStateArfcnRsrpBoost(mNewSS,
+                        networkRegState.getCellIdentity());
                 break;
             }
 
@@ -3329,7 +3244,7 @@
         switch (mCi.getRadioState()) {
             case TelephonyManager.RADIO_POWER_UNAVAILABLE:
                 mNewSS.setStateOutOfService();
-                setSignalStrengthDefaultValues();
+                mPhone.getSignalStrengthController().setSignalStrengthDefaultValues();
                 mLastNitzData = null;
                 mNitzState.handleNetworkUnavailable();
                 pollStateDone();
@@ -3337,7 +3252,7 @@
 
             case TelephonyManager.RADIO_POWER_OFF:
                 mNewSS.setStateOff();
-                setSignalStrengthDefaultValues();
+                mPhone.getSignalStrengthController().setSignalStrengthDefaultValues();
                 mLastNitzData = null;
                 mNitzState.handleNetworkUnavailable();
                 // don't poll when device is shutting down or the poll was not modemTrigged
@@ -3567,6 +3482,8 @@
 
         boolean hasCssIndicatorChanged = (mSS.getCssIndicator() != mNewSS.getCssIndicator());
 
+        boolean hasBandwidthChanged = mSS.getCellBandwidths() != mNewSS.getCellBandwidths();
+
         boolean has4gHandoff = false;
         boolean hasMultiApnSupport = false;
         boolean hasLostMultiApnSupport = false;
@@ -3610,6 +3527,7 @@
                     + " hasCssIndicatorChanged = " + hasCssIndicatorChanged
                     + " hasNrFrequencyRangeChanged = " + hasNrFrequencyRangeChanged
                     + " hasNrStateChanged = " + hasNrStateChanged
+                    + " hasBandwidthChanged = " + hasBandwidthChanged
                     + " hasAirplaneModeOnlChanged = " + hasAirplaneModeOnChanged);
         }
 
@@ -3686,6 +3604,10 @@
             mCssIndicatorChangedRegistrants.notifyRegistrants();
         }
 
+        if (hasBandwidthChanged) {
+            mBandwidthChangedRegistrants.notifyRegistrants();
+        }
+
         if (hasRejectCauseChanged) {
             setNotification(CS_REJECT_CAUSE_ENABLED);
         }
@@ -3781,13 +3703,15 @@
 
         if (hasRilVoiceRadioTechnologyChanged) {
             shouldLogRatChange = true;
-            notifySignalStrength();
+            // TODO(b/178429976): Remove the dependency on SSC. Double check if the SS broadcast
+            // is really needed when CS/PS RAT change.
+            mPhone.getSignalStrengthController().notifySignalStrength();
         }
 
         for (int transport : mTransportManager.getAvailableTransports()) {
             if (hasRilDataRadioTechnologyChanged.get(transport)) {
                 shouldLogRatChange = true;
-                notifySignalStrength();
+                mPhone.getSignalStrengthController().notifySignalStrength();
             }
 
             if (hasDataRegStateChanged.get(transport)
@@ -3818,7 +3742,8 @@
         // because the signal strength might come earlier RAT and radio state
         // changed.
         if (hasAirplaneModeOffChanged) {
-            mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
+            // TODO(b/178429976): Remove the dependency on SSC. This should be done in SSC.
+            mPhone.getSignalStrengthController().getSignalStrengthFromCi();
         }
 
         if (shouldLogAttachedChange) {
@@ -4413,20 +4338,24 @@
     }
 
     /**
-     * nitzReceiveTime is time_t that the NITZ time was posted
+     * Handle the NITZ string from the modem
+     *
+     * @param nitzString NITZ time string in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt"
+     * @param nitzReceiveTimeMs time according to {@link android.os.SystemClock#elapsedRealtime()}
+     *        when the RIL sent the NITZ time to the framework
+     * @param ageMs time in milliseconds indicating how long NITZ was cached in RIL and modem
      */
-    private void setTimeFromNITZString(String nitzString, long nitzReceiveTime) {
+    private void setTimeFromNITZString(String nitzString, long nitzReceiveTimeMs, long ageMs) {
         long start = SystemClock.elapsedRealtime();
         if (DBG) {
-            Rlog.d(LOG_TAG, "NITZ: " + nitzString + "," + nitzReceiveTime
-                    + " start=" + start + " delay=" + (start - nitzReceiveTime));
+            Rlog.d(LOG_TAG, "NITZ: " + nitzString + "," + nitzReceiveTimeMs + ", ageMs=" + ageMs
+                    + " start=" + start + " delay=" + (start - nitzReceiveTimeMs));
         }
         NitzData newNitzData = NitzData.parse(nitzString);
         mLastNitzData = newNitzData;
         if (newNitzData != null) {
             try {
-                TimestampedValue<NitzData> nitzSignal =
-                        new TimestampedValue<>(nitzReceiveTime, newNitzData);
+                NitzSignal nitzSignal = new NitzSignal(nitzReceiveTimeMs, newNitzData, ageMs);
                 mNitzState.handleNitzReceived(nitzSignal);
             } finally {
                 if (DBG) {
@@ -4673,28 +4602,7 @@
     }
 
     private void queueNextSignalStrengthPoll() {
-        if (mDontPollSignalStrength) {
-            // The radio is telling us about signal strength changes
-            // we don't have to ask it
-            return;
-        }
-
-        // if there is no SIM present, do not poll signal strength
-        UiccCard uiccCard = UiccController.getInstance().getUiccCard(getPhoneId());
-        if (uiccCard == null || uiccCard.getCardState() == CardState.CARDSTATE_ABSENT) {
-            log("Not polling signal strength due to absence of SIM");
-            return;
-        }
-
-        Message msg;
-
-        msg = obtainMessage();
-        msg.what = EVENT_POLL_SIGNAL_STRENGTH;
-
-        long nextTime;
-
-        // TODO Don't poll signal strength if screen is off
-        sendMessageDelayed(msg, POLL_PERIOD_MILLIS);
+        mPhone.getSignalStrengthController().queueNextSignalStrengthPoll();
     }
 
     private void notifyCdmaSubscriptionInfoReady() {
@@ -5036,76 +4944,6 @@
         }
     }
 
-    /**
-     * Checks if the provided earfcn falls withing the range of earfcns.
-     *
-     * return int index in earfcnPairList if earfcn falls within the provided range; -1 otherwise.
-     */
-    private int containsEarfcnInEarfcnRange(ArrayList<Pair<Integer, Integer>> earfcnPairList,
-            int earfcn) {
-        int index = 0;
-        if (earfcnPairList != null) {
-            for (Pair<Integer, Integer> earfcnPair : earfcnPairList) {
-                if ((earfcn >= earfcnPair.first) && (earfcn <= earfcnPair.second)) {
-                    return index;
-                }
-                index++;
-            }
-        }
-
-        return -1;
-    }
-
-    /**
-     * Convert the earfcnStringArray to list of pairs.
-     *
-     * Format of the earfcnsList is expected to be {"erafcn1_start-earfcn1_end",
-     * "earfcn2_start-earfcn2_end" ... }
-     */
-    ArrayList<Pair<Integer, Integer>> convertEarfcnStringArrayToPairList(String[] earfcnsList) {
-        ArrayList<Pair<Integer, Integer>> earfcnPairList = new ArrayList<Pair<Integer, Integer>>();
-
-        if (earfcnsList != null) {
-            int earfcnStart;
-            int earfcnEnd;
-            for (int i = 0; i < earfcnsList.length; i++) {
-                try {
-                    String[] earfcns = earfcnsList[i].split("-");
-                    if (earfcns.length != 2) {
-                        if (VDBG) {
-                            log("Invalid earfcn range format");
-                        }
-                        return null;
-                    }
-
-                    earfcnStart = Integer.parseInt(earfcns[0]);
-                    earfcnEnd = Integer.parseInt(earfcns[1]);
-
-                    if (earfcnStart > earfcnEnd) {
-                        if (VDBG) {
-                            log("Invalid earfcn range format");
-                        }
-                        return null;
-                    }
-
-                    earfcnPairList.add(new Pair<Integer, Integer>(earfcnStart, earfcnEnd));
-                } catch (PatternSyntaxException pse) {
-                    if (VDBG) {
-                        log("Invalid earfcn range format");
-                    }
-                    return null;
-                } catch (NumberFormatException nfe) {
-                    if (VDBG) {
-                        log("Invalid earfcn number format");
-                    }
-                    return null;
-                }
-            }
-        }
-
-        return earfcnPairList;
-    }
-
     private void onCarrierConfigChanged() {
         PersistableBundle config = getCarrierConfig();
         log("CarrierConfigChange " + config);
@@ -5116,8 +4954,10 @@
             mCdnr.updateEfForEri(getOperatorNameFromEri());
         }
 
-        updateArfcnLists(config);
+        // TODO(b/178429976): Listen config change in SSC and remove logic here
+        mPhone.getSignalStrengthController().updateArfcnLists(config);
         mPhone.getSignalStrengthController().updateReportingCriteria(config);
+
         updateOperatorNamePattern(config);
         mCdnr.updateEfFromCarrierConfig(config);
         mPhone.notifyCallForwardingIndicator();
@@ -5128,93 +4968,6 @@
         pollStateInternal(false);
     }
 
-    private void updateArfcnLists(PersistableBundle config) {
-        synchronized (mRsrpBoostLock) {
-            mLteRsrpBoost = config.getInt(CarrierConfigManager.KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
-            String[] earfcnsStringArrayForRsrpBoost = config.getStringArray(
-                    CarrierConfigManager.KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY);
-            mEarfcnPairListForRsrpBoost = convertEarfcnStringArrayToPairList(
-                    earfcnsStringArrayForRsrpBoost);
-
-            mNrRsrpBoost = config.getIntArray(
-                    CarrierConfigManager.KEY_NRARFCNS_RSRP_BOOST_INT_ARRAY);
-            String[] nrarfcnsStringArrayForRsrpBoost = config.getStringArray(
-                    CarrierConfigManager.KEY_BOOSTED_NRARFCNS_STRING_ARRAY);
-            mNrarfcnRangeListForRsrpBoost = convertEarfcnStringArrayToPairList(
-                    nrarfcnsStringArrayForRsrpBoost);
-
-            if ((mNrRsrpBoost == null && mNrarfcnRangeListForRsrpBoost != null)
-                    || (mNrRsrpBoost != null && mNrarfcnRangeListForRsrpBoost == null)
-                    || (mNrRsrpBoost != null && mNrarfcnRangeListForRsrpBoost != null
-                    && mNrRsrpBoost.length != mNrarfcnRangeListForRsrpBoost.size())) {
-                loge("Invalid parameters for NR RSRP boost");
-                mNrRsrpBoost = null;
-                mNrarfcnRangeListForRsrpBoost = null;
-            }
-        }
-    }
-
-    private void updateServiceStateArfcnRsrpBoost(ServiceState serviceState,
-            CellIdentity cellIdentity) {
-        int rsrpBoost = 0;
-        int arfcn;
-
-        synchronized (mRsrpBoostLock) {
-            switch (cellIdentity.getType()) {
-                case CellInfo.TYPE_LTE:
-                    arfcn = ((CellIdentityLte) cellIdentity).getEarfcn();
-                    if (arfcn != INVALID_ARFCN
-                            && containsEarfcnInEarfcnRange(mEarfcnPairListForRsrpBoost,
-                            arfcn) != -1) {
-                        rsrpBoost = mLteRsrpBoost;
-                    }
-                    break;
-                case CellInfo.TYPE_NR:
-                    arfcn = ((CellIdentityNr) cellIdentity).getNrarfcn();
-                    if (arfcn != INVALID_ARFCN) {
-                        int index = containsEarfcnInEarfcnRange(mNrarfcnRangeListForRsrpBoost,
-                                arfcn);
-                        if (index != -1) {
-                            rsrpBoost = mNrRsrpBoost[index];
-                        }
-                    }
-                    break;
-                default:
-                    break;
-            }
-        }
-        serviceState.setArfcnRsrpBoost(rsrpBoost);
-    }
-
-    /**
-     * send signal-strength-changed notification if changed Called both for
-     * solicited and unsolicited signal strength updates
-     *
-     * @return true if the signal strength changed and a notification was sent.
-     */
-    protected boolean onSignalStrengthResult(AsyncResult ar) {
-
-        // This signal is used for both voice and data radio signal so parse
-        // all fields
-        // Under power off, let's suppress valid signal strength report, which is
-        // beneficial to avoid icon flickering.
-        if ((ar.exception == null) && (ar.result != null)
-                && mSS.getState() != ServiceState.STATE_POWER_OFF) {
-            mSignalStrength = (SignalStrength) ar.result;
-
-            PersistableBundle config = getCarrierConfig();
-            mSignalStrength.updateLevel(config, mSS);
-        } else {
-            log("onSignalStrengthResult() Exception from RIL : " + ar.exception);
-            mSignalStrength = new SignalStrength();
-        }
-        mSignalStrengthUpdatedTime = System.currentTimeMillis();
-
-        boolean ssChanged = notifySignalStrength();
-
-        return ssChanged;
-    }
-
     /**
      * Hang up all voice call and turn off radio. Implemented by derived class.
      */
@@ -5338,50 +5091,6 @@
     }
 
     /**
-     * @return signal strength
-     */
-    public SignalStrength getSignalStrength() {
-        if (shouldRefreshSignalStrength()) {
-            log("SST.getSignalStrength() refreshing signal strength.");
-            obtainMessage(EVENT_POLL_SIGNAL_STRENGTH).sendToTarget();
-        }
-        return mSignalStrength;
-    }
-
-    private boolean shouldRefreshSignalStrength() {
-        long curTime = System.currentTimeMillis();
-
-        // If last signal strength is older than 10 seconds, or somehow if curTime is smaller
-        // than mSignalStrengthUpdatedTime (system time update), it's considered stale.
-        boolean isStale = (mSignalStrengthUpdatedTime > curTime)
-                || (curTime - mSignalStrengthUpdatedTime > SIGNAL_STRENGTH_REFRESH_THRESHOLD_IN_MS);
-        if (!isStale) return false;
-
-        List<SubscriptionInfo> subInfoList = SubscriptionController.getInstance()
-                .getActiveSubscriptionInfoList(mPhone.getContext().getOpPackageName(),
-                        mPhone.getContext().getAttributionTag());
-
-        if (!ArrayUtils.isEmpty(subInfoList)) {
-            for (SubscriptionInfo info : subInfoList) {
-                // If we have an active opportunistic subscription whose data is IN_SERVICE,
-                // we need to get signal strength to decide data switching threshold. In this case,
-                // we poll latest signal strength from modem.
-                if (info.isOpportunistic()) {
-                    TelephonyManager tm = TelephonyManager.from(mPhone.getContext())
-                            .createForSubscriptionId(info.getSubscriptionId());
-                    ServiceState ss = tm.getServiceState();
-                    if (ss != null
-                            && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
-                        return true;
-                    }
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /**
      * Registration point for subscription info ready
      * @param h handler to notify
      * @param what what code of message when delivered
@@ -5432,25 +5141,6 @@
         }
     }
 
-    private void dumpEarfcnPairList(PrintWriter pw, ArrayList<Pair<Integer, Integer>> pairList,
-            String name) {
-        pw.print(" " + name + "={");
-        if (pairList != null) {
-            int i = pairList.size();
-            for (Pair<Integer, Integer> earfcnPair : pairList) {
-                pw.print("(");
-                pw.print(earfcnPair.first);
-                pw.print(",");
-                pw.print(earfcnPair.second);
-                pw.print(")");
-                if ((--i) != 0) {
-                    pw.print(",");
-                }
-            }
-        }
-        pw.println("}");
-    }
-
     private void dumpCellInfoList(PrintWriter pw) {
         pw.print(" mLastCellInfoList={");
         if(mLastCellInfoList != null) {
@@ -5476,9 +5166,6 @@
         pw.println(" mPollingContext=" + mPollingContext + " - " +
                 (mPollingContext != null ? mPollingContext[0] : ""));
         pw.println(" mDesiredPowerState=" + mDesiredPowerState);
-        pw.println(" mDontPollSignalStrength=" + mDontPollSignalStrength);
-        pw.println(" mSignalStrength=" + mSignalStrength);
-        pw.println(" mLastSignalStrength=" + mLastSignalStrength);
         pw.println(" mRestrictedState=" + mRestrictedState);
         pw.println(" mPendingRadioPowerOffAfterDataOff=" + mPendingRadioPowerOffAfterDataOff);
         pw.println(" mPendingRadioPowerOffAfterDataOffTag=" + mPendingRadioPowerOffAfterDataOffTag);
@@ -5533,12 +5220,8 @@
         pw.println(" mRadioDisabledByCarrier" + mRadioDisabledByCarrier);
         pw.println(" mDeviceShuttingDown=" + mDeviceShuttingDown);
         pw.println(" mSpnUpdatePending=" + mSpnUpdatePending);
-        pw.println(" mLteRsrpBoost=" + mLteRsrpBoost);
-        pw.println(" mNrRsrpBoost=" + Arrays.toString(mNrRsrpBoost));
         pw.println(" mCellInfoMinIntervalMs=" + mCellInfoMinIntervalMs);
         pw.println(" mEriManager=" + mEriManager);
-        dumpEarfcnPairList(pw, mEarfcnPairListForRsrpBoost, "mEarfcnPairListForRsrpBoost");
-        dumpEarfcnPairList(pw, mNrarfcnRangeListForRsrpBoost, "mNrarfcnRangeListForRsrpBoost");
 
         mLocaleTracker.dump(fd, pw, args);
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
@@ -5736,12 +5419,6 @@
         }
     }
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private void setSignalStrengthDefaultValues() {
-        mSignalStrength = new SignalStrength();
-        mSignalStrengthUpdatedTime = System.currentTimeMillis();
-    }
-
     protected String getHomeOperatorNumeric() {
         String numeric = ((TelephonyManager) mPhone.getContext().
                 getSystemService(Context.TELEPHONY_SERVICE)).
@@ -6090,6 +5767,25 @@
     }
 
     /**
+     * Registers for cell bandwidth changed.
+     * @param h handler to notify
+     * @param what what code of message when delivered
+     * @param obj placed in Message.obj
+     */
+    public void registerForBandwidthChanged(Handler h, int what, Object obj) {
+        Registrant r = new Registrant(h, what, obj);
+        mBandwidthChangedRegistrants.add(r);
+    }
+
+    /**
+     * Unregisters for cell bandwidth changed.
+     * @param h handler to notify
+     */
+    public void unregisterForBandwidthChanged(Handler h) {
+        mBandwidthChangedRegistrants.remove(h);
+    }
+
+    /**
      * Get the NR data connection context ids.
      *
      * @return data connection context ids.
diff --git a/src/java/com/android/internal/telephony/SignalStrengthController.java b/src/java/com/android/internal/telephony/SignalStrengthController.java
index 48d91c7..d99f4fc 100644
--- a/src/java/com/android/internal/telephony/SignalStrengthController.java
+++ b/src/java/com/android/internal/telephony/SignalStrengthController.java
@@ -27,41 +27,96 @@
 import android.os.RemoteException;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
+import android.telephony.CellIdentity;
+import android.telephony.CellIdentityLte;
+import android.telephony.CellIdentityNr;
+import android.telephony.CellInfo;
 import android.telephony.CellSignalStrengthLte;
 import android.telephony.CellSignalStrengthNr;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
 import android.telephony.SignalStrengthUpdateRequest;
 import android.telephony.SignalThresholdInfo;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.uicc.IccCardStatus;
+import com.android.internal.telephony.uicc.UiccCard;
+import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.PatternSyntaxException;
 
 /**
  * SignalStrengthController handles signal polling request and unsolicited signal strength update.
  */
 public class SignalStrengthController extends Handler {
-    protected static final boolean DBG = false; /* STOPSHIP if true */
-    protected static final String TAG = "SSCtr";
+    private static final boolean DBG = false; /* STOPSHIP if true */
+    private static final String TAG = "SSCtr";
 
-    private static final int EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST   = 1;
-    private static final int EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST = 2;
-    private static final int EVENT_ON_DEVICE_IDLE_STATE_CHANGED         = 3;
-    private static final int EVENT_RIL_CONNECTED                        = 4;
-    private static final int EVENT_RADIO_AVAILABLE                      = 5;
+    private static final long SIGNAL_STRENGTH_REFRESH_THRESHOLD_IN_MS =
+            TimeUnit.SECONDS.toMillis(10);
+    /** Signal strength poll rate. */
+    private static final long POLL_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(20);
+    private static final int INVALID_ARFCN = -1;
 
+    private static final int EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST       = 1;
+    private static final int EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST     = 2;
+    private static final int EVENT_ON_DEVICE_IDLE_STATE_CHANGED             = 3;
+    private static final int EVENT_RIL_CONNECTED                            = 4;
+    private static final int EVENT_RADIO_AVAILABLE                          = 5;
+    private static final int EVENT_GET_SIGNAL_STRENGTH                      = 6;
+    private static final int EVENT_POLL_SIGNAL_STRENGTH                     = 7;
+    private static final int EVENT_SIGNAL_STRENGTH_UPDATE                   = 8;
 
     private final Phone mPhone;
     private final CommandsInterface mCi;
 
+    /**
+     * By default, strength polling is enabled.  However, if we're
+     * getting unsolicited signal strength updates from the radio, set
+     * value to true and don't bother polling any more.
+     */
+    private boolean mDontPollSignalStrength = false;
+    @NonNull
+    private SignalStrength mSignalStrength;
+    private long mSignalStrengthUpdatedTime;
+    private SignalStrength mLastSignalStrength = null;
+
+    /**
+     * List of LTE EARFCNs (E-UTRAN Absolute Radio Frequency Channel Number,
+     * Reference: 3GPP TS 36.104 5.4.3)
+     * inclusive ranges for which the lte rsrp boost is applied
+     */
+    private ArrayList<Pair<Integer, Integer>> mEarfcnPairListForRsrpBoost = null;
+    /**
+     * Offset which is reduced from the rsrp threshold while calculating signal strength level.
+     */
+    private int mLteRsrpBoost = 0;
+    /**
+     * Ranges of NR ARFCNs (5G Absolute Radio Frequency Channel Number,
+     * Reference: 3GPP TS 38.104)
+     * inclusive ranges for which the corresponding nr rsrp boost is applied
+     */
+    private ArrayList<Pair<Integer, Integer>> mNrarfcnRangeListForRsrpBoost = null;
+    @Nullable
+    private int[] mNrRsrpBoost = null;
+    private final Object mRsrpBoostLock = new Object();
+
     private final List<SignalRequestRecord> mSignalRequestRecords = new ArrayList<>();
 
     public SignalStrengthController(Phone phone) {
@@ -70,11 +125,14 @@
 
         mCi.registerForRilConnected(this, EVENT_RIL_CONNECTED, null);
         mCi.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
+        mCi.setOnSignalStrengthUpdate(this, EVENT_SIGNAL_STRENGTH_UPDATE, null);
+        setSignalStrengthDefaultValues();
     }
 
     @Override
     public void handleMessage(Message msg) {
         if (DBG) log("received event " + msg.what);
+        AsyncResult ar;
 
         switch (msg.what) {
             case EVENT_RIL_CONNECTED: // fall through
@@ -147,30 +205,154 @@
                 break;
             }
 
+            case EVENT_GET_SIGNAL_STRENGTH: {
+                // This callback is called when signal strength is polled
+                // all by itself
+
+                if (!(mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON)) {
+                    // Polling will continue when radio turns back on
+                    return;
+                }
+                ar = (AsyncResult) msg.obj;
+                onSignalStrengthResult(ar);
+                queueNextSignalStrengthPoll();
+
+                break;
+            }
+
+            case EVENT_POLL_SIGNAL_STRENGTH: {
+                // Just poll signal strength...not part of pollState()
+
+                mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
+                break;
+            }
+
+            case EVENT_SIGNAL_STRENGTH_UPDATE: {
+                // This is a notification from CommandsInterface.setOnSignalStrengthUpdate
+
+                ar = (AsyncResult) msg.obj;
+
+                // The radio is telling us about signal strength changes
+                // we don't have to ask it
+                mDontPollSignalStrength = true;
+
+                onSignalStrengthResult(ar);
+                break;
+            }
+
             default:
                 log("Unhandled message with number: " + msg.what);
                 break;
         }
     }
 
-    /**
-     * Set a new request to update the signal strength thresholds.
-     */
-    public void setSignalStrengthUpdateRequest(int subId, int callingUid,
-            SignalStrengthUpdateRequest request, @NonNull Message onCompleted) {
-        SignalRequestRecord record = new SignalRequestRecord(subId, callingUid, request);
-        sendMessage(obtainMessage(EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST,
-                new Pair<SignalRequestRecord, Message>(record, onCompleted)));
+    void dispose() {
+        mCi.unSetOnSignalStrengthUpdate(this);
     }
 
     /**
-     * Clear the previously set request.
+     * Called when RIL is connected during boot up or after modem restart. Set the default criteria
+     * so that modem can start with default state before updated criteria is ready.
      */
-    public void clearSignalStrengthUpdateRequest(int subId, int callingUid,
-            SignalStrengthUpdateRequest request, @Nullable Message onCompleted) {
-        SignalRequestRecord record = new SignalRequestRecord(subId, callingUid, request);
-        sendMessage(obtainMessage(EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST,
-                new Pair<SignalRequestRecord, Message>(record, onCompleted)));
+    private void onReset() {
+        setDefaultSignalStrengthReportingCriteria();
+    }
+
+    void getSignalStrengthFromCi() {
+        mCi.getSignalStrength(obtainMessage(EVENT_GET_SIGNAL_STRENGTH));
+    }
+
+    /**
+     * send signal-strength-changed notification if changed Called both for
+     * solicited and unsolicited signal strength updates
+     *
+     * @return true if the signal strength changed and a notification was sent.
+     */
+    private boolean onSignalStrengthResult(AsyncResult ar) {
+
+        // This signal is used for both voice and data radio signal so parse
+        // all fields
+
+        if ((ar.exception == null) && (ar.result != null)) {
+            mSignalStrength = (SignalStrength) ar.result;
+
+            PersistableBundle config = getCarrierConfig();
+            if (mPhone.getServiceStateTracker() != null) {
+                mSignalStrength.updateLevel(config, mPhone.getServiceStateTracker().mSS);
+            }
+        } else {
+            log("onSignalStrengthResult() Exception from RIL : " + ar.exception);
+            mSignalStrength = new SignalStrength();
+        }
+        mSignalStrengthUpdatedTime = System.currentTimeMillis();
+
+        boolean ssChanged = notifySignalStrength();
+
+        return ssChanged;
+    }
+
+    /**
+     * @return signal strength
+     */
+    public SignalStrength getSignalStrength() {
+        if (shouldRefreshSignalStrength()) {
+            log("getSignalStrength() refreshing signal strength.");
+            obtainMessage(EVENT_POLL_SIGNAL_STRENGTH).sendToTarget();
+        }
+        return mSignalStrength;
+    }
+
+    private boolean shouldRefreshSignalStrength() {
+        long curTime = System.currentTimeMillis();
+
+        // If last signal strength is older than 10 seconds, or somehow if curTime is smaller
+        // than mSignalStrengthUpdatedTime (system time update), it's considered stale.
+        boolean isStale = (mSignalStrengthUpdatedTime > curTime)
+                || (curTime - mSignalStrengthUpdatedTime > SIGNAL_STRENGTH_REFRESH_THRESHOLD_IN_MS);
+        if (!isStale) return false;
+
+        List<SubscriptionInfo> subInfoList = SubscriptionController.getInstance()
+                .getActiveSubscriptionInfoList(mPhone.getContext().getOpPackageName(),
+                        mPhone.getContext().getAttributionTag());
+
+        if (!ArrayUtils.isEmpty(subInfoList)) {
+            for (SubscriptionInfo info : subInfoList) {
+                // If we have an active opportunistic subscription whose data is IN_SERVICE,
+                // we need to get signal strength to decide data switching threshold. In this case,
+                // we poll latest signal strength from modem.
+                if (info.isOpportunistic()) {
+                    TelephonyManager tm = TelephonyManager.from(mPhone.getContext())
+                            .createForSubscriptionId(info.getSubscriptionId());
+                    ServiceState ss = tm.getServiceState();
+                    if (ss != null
+                            && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    void queueNextSignalStrengthPoll() {
+        if (mDontPollSignalStrength) {
+            // The radio is telling us about signal strength changes
+            // we don't have to ask it
+            return;
+        }
+
+        // if there is no SIM present, do not poll signal strength
+        UiccCard uiccCard = UiccController.getInstance().getUiccCard(
+                mPhone != null ? mPhone.getPhoneId() : SubscriptionManager.DEFAULT_PHONE_INDEX);
+        if (uiccCard == null
+                || uiccCard.getCardState() == IccCardStatus.CardState.CARDSTATE_ABSENT) {
+            log("Not polling signal strength due to absence of SIM");
+            return;
+        }
+
+        // TODO Don't poll signal strength if screen is off
+        sendMessageDelayed(obtainMessage(EVENT_POLL_SIGNAL_STRENGTH), POLL_PERIOD_MILLIS);
     }
 
     /**
@@ -223,6 +405,65 @@
         }
     }
 
+    private void setDefaultSignalStrengthReportingCriteria() {
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
+                AccessNetworkThresholds.GERAN, AccessNetworkConstants.AccessNetworkType.GERAN,
+                true);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP,
+                AccessNetworkThresholds.UTRAN, AccessNetworkConstants.AccessNetworkType.UTRAN,
+                true);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
+                AccessNetworkThresholds.EUTRAN_RSRP,
+                AccessNetworkConstants.AccessNetworkType.EUTRAN, true);
+        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
+                AccessNetworkThresholds.CDMA2000, AccessNetworkConstants.AccessNetworkType.CDMA2000,
+                true);
+        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
+                    AccessNetworkThresholds.EUTRAN_RSRQ,
+                    AccessNetworkConstants.AccessNetworkType.EUTRAN, false);
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR,
+                    AccessNetworkThresholds.EUTRAN_RSSNR,
+                    AccessNetworkConstants.AccessNetworkType.EUTRAN, true);
+
+            // Defaultly we only need SSRSRP for NGRAN signal criteria reporting
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
+                    AccessNetworkThresholds.NGRAN_RSRSRP,
+                    AccessNetworkConstants.AccessNetworkType.NGRAN, true);
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
+                    AccessNetworkThresholds.NGRAN_RSRSRQ,
+                    AccessNetworkConstants.AccessNetworkType.NGRAN, false);
+            mPhone.setSignalStrengthReportingCriteria(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR,
+                    AccessNetworkThresholds.NGRAN_SSSINR,
+                    AccessNetworkConstants.AccessNetworkType.NGRAN, false);
+        }
+    }
+
+    void setSignalStrengthDefaultValues() {
+        mSignalStrength = new SignalStrength();
+        mSignalStrengthUpdatedTime = System.currentTimeMillis();
+    }
+
+    boolean notifySignalStrength() {
+        boolean notified = false;
+        if (!mSignalStrength.equals(mLastSignalStrength)) {
+            try {
+                mPhone.notifySignalStrength();
+                notified = true;
+                mLastSignalStrength = mSignalStrength;
+            } catch (NullPointerException ex) {
+                log("updateSignalStrength() Phone already destroyed: " + ex
+                        + "SignalStrength not notified");
+            }
+        }
+        return notified;
+    }
+
     /**
      * Print the SignalStrengthController states into the given stream.
      *
@@ -233,11 +474,57 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         ipw.increaseIndent();
-        pw.println("mSignalRequestRecords: " + mSignalRequestRecords);
+        pw.println("mSignalRequestRecords=" + mSignalRequestRecords);
+        pw.println(" mLastSignalStrength=" + mLastSignalStrength);
+        pw.println(" mSignalStrength=" + mSignalStrength);
+        pw.println(" mDontPollSignalStrength=" + mDontPollSignalStrength);
+        pw.println(" mLteRsrpBoost=" + mLteRsrpBoost);
+        pw.println(" mNrRsrpBoost=" + Arrays.toString(mNrRsrpBoost));
+        dumpEarfcnPairList(pw, mEarfcnPairListForRsrpBoost, "mEarfcnPairListForRsrpBoost");
+        dumpEarfcnPairList(pw, mNrarfcnRangeListForRsrpBoost, "mNrarfcnRangeListForRsrpBoost");
         ipw.decreaseIndent();
         ipw.flush();
     }
 
+    private void dumpEarfcnPairList(PrintWriter pw, ArrayList<Pair<Integer, Integer>> pairList,
+            String name) {
+        pw.print(" " + name + "={");
+        if (pairList != null) {
+            int i = pairList.size();
+            for (Pair<Integer, Integer> earfcnPair : pairList) {
+                pw.print("(");
+                pw.print(earfcnPair.first);
+                pw.print(",");
+                pw.print(earfcnPair.second);
+                pw.print(")");
+                if ((--i) != 0) {
+                    pw.print(",");
+                }
+            }
+        }
+        pw.println("}");
+    }
+
+    /**
+     * Set a new request to update the signal strength thresholds.
+     */
+    public void setSignalStrengthUpdateRequest(int subId, int callingUid,
+            SignalStrengthUpdateRequest request, @NonNull Message onCompleted) {
+        SignalRequestRecord record = new SignalRequestRecord(subId, callingUid, request);
+        sendMessage(obtainMessage(EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST,
+                new Pair<SignalRequestRecord, Message>(record, onCompleted)));
+    }
+
+    /**
+     * Clear the previously set request.
+     */
+    public void clearSignalStrengthUpdateRequest(int subId, int callingUid,
+            SignalStrengthUpdateRequest request, @Nullable Message onCompleted) {
+        SignalRequestRecord record = new SignalRequestRecord(subId, callingUid, request);
+        sendMessage(obtainMessage(EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST,
+                new Pair<SignalRequestRecord, Message>(record, onCompleted)));
+    }
+
     /**
      * Align all the qualified thresholds set from applications to the {@code systemThresholds}
      * and consolidate a new thresholds array, follow rules below:
@@ -334,43 +621,203 @@
         return false;
     }
 
-    private void setDefaultSignalStrengthReportingCriteria() {
-        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
-                AccessNetworkThresholds.GERAN, AccessNetworkConstants.AccessNetworkType.GERAN,
-                true);
-        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP,
-                AccessNetworkThresholds.UTRAN, AccessNetworkConstants.AccessNetworkType.UTRAN,
-                true);
-        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
-                AccessNetworkThresholds.EUTRAN_RSRP,
-                AccessNetworkConstants.AccessNetworkType.EUTRAN, true);
-        mPhone.setSignalStrengthReportingCriteria(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
-                AccessNetworkThresholds.CDMA2000, AccessNetworkConstants.AccessNetworkType.CDMA2000,
-                true);
-        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
-                    AccessNetworkThresholds.EUTRAN_RSRQ,
-                    AccessNetworkConstants.AccessNetworkType.EUTRAN, false);
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR,
-                    AccessNetworkThresholds.EUTRAN_RSSNR,
-                    AccessNetworkConstants.AccessNetworkType.EUTRAN, true);
+    private static boolean isRanAndSignalMeasurementTypeMatch(
+            @AccessNetworkConstants.RadioAccessNetworkType int ran,
+            @SignalThresholdInfo.SignalMeasurementType int measurement,
+            SignalThresholdInfo info) {
+        return ran == info.getRadioAccessNetworkType()
+                && measurement == info.getSignalMeasurementType();
+    }
 
-            // Defaultly we only need SSRSRP for NGRAN signal criteria reporting
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
-                    AccessNetworkThresholds.NGRAN_RSRSRP,
-                    AccessNetworkConstants.AccessNetworkType.NGRAN, true);
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
-                    AccessNetworkThresholds.NGRAN_RSRSRQ,
-                    AccessNetworkConstants.AccessNetworkType.NGRAN, false);
-            mPhone.setSignalStrengthReportingCriteria(
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR,
-                    AccessNetworkThresholds.NGRAN_SSSINR,
-                    AccessNetworkConstants.AccessNetworkType.NGRAN, false);
+    private static boolean isSignalReportRequestedWhileIdle(SignalStrengthUpdateRequest request) {
+        return request.isSystemThresholdReportingRequestedWhileIdle()
+                || request.isReportingRequestedWhileIdle();
+    }
+
+    /**
+     * Gets the carrier configuration values for a particular subscription.
+     *
+     * @return A {@link PersistableBundle} containing the config for the given subId,
+     *         or default values for an invalid subId.
+     */
+    @NonNull
+    private PersistableBundle getCarrierConfig() {
+        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            // If an invalid subId is used, this bundle will contain default values.
+            PersistableBundle config = configManager.getConfigForSubId(mPhone.getSubId());
+            if (config != null) {
+                return config;
+            }
         }
+        // Return static default defined in CarrierConfigManager.
+        return CarrierConfigManager.getDefaultConfig();
+    }
+
+    private class SignalRequestRecord implements IBinder.DeathRecipient {
+        final int mSubId; // subId the request originally applied to
+        final int mCallingUid;
+        final SignalStrengthUpdateRequest mRequest;
+
+        SignalRequestRecord(int subId, int uid, @NonNull SignalStrengthUpdateRequest request) {
+            this.mCallingUid = uid;
+            this.mSubId = subId;
+            this.mRequest = request;
+        }
+
+        @Override
+        public void binderDied() {
+            clearSignalStrengthUpdateRequest(mSubId, mCallingUid, mRequest, null /*onCompleted*/);
+        }
+
+        @Override
+        public String toString() {
+            StringBuffer sb = new StringBuffer("SignalRequestRecord {");
+            sb.append("mSubId=").append(mSubId);
+            sb.append(" mCallingUid=").append(mCallingUid);
+            sb.append(" mRequest=").append(mRequest).append("}");
+            return sb.toString();
+        }
+    }
+
+    private void updateAlwaysReportSignalStrength() {
+        final int curSubId = mPhone.getSubId();
+        boolean alwaysReport = mSignalRequestRecords.stream().anyMatch(
+                srr -> srr.mSubId == curSubId && isSignalReportRequestedWhileIdle(srr.mRequest));
+
+        // TODO(b/177924721): TM#setAlwaysReportSignalStrength will be removed and we will not
+        // worry about unset flag which was set by other client.
+        mPhone.setAlwaysReportSignalStrength(alwaysReport);
+    }
+
+    void updateArfcnLists(PersistableBundle config) {
+        synchronized (mRsrpBoostLock) {
+            mLteRsrpBoost = config.getInt(CarrierConfigManager.KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
+            String[] earfcnsStringArrayForRsrpBoost = config.getStringArray(
+                    CarrierConfigManager.KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY);
+            mEarfcnPairListForRsrpBoost = convertEarfcnStringArrayToPairList(
+                    earfcnsStringArrayForRsrpBoost);
+
+            mNrRsrpBoost = config.getIntArray(
+                    CarrierConfigManager.KEY_NRARFCNS_RSRP_BOOST_INT_ARRAY);
+            String[] nrarfcnsStringArrayForRsrpBoost = config.getStringArray(
+                    CarrierConfigManager.KEY_BOOSTED_NRARFCNS_STRING_ARRAY);
+            mNrarfcnRangeListForRsrpBoost = convertEarfcnStringArrayToPairList(
+                    nrarfcnsStringArrayForRsrpBoost);
+
+            if ((mNrRsrpBoost == null && mNrarfcnRangeListForRsrpBoost != null)
+                    || (mNrRsrpBoost != null && mNrarfcnRangeListForRsrpBoost == null)
+                    || (mNrRsrpBoost != null && mNrarfcnRangeListForRsrpBoost != null
+                    && mNrRsrpBoost.length != mNrarfcnRangeListForRsrpBoost.size())) {
+                loge("Invalid parameters for NR RSRP boost");
+                mNrRsrpBoost = null;
+                mNrarfcnRangeListForRsrpBoost = null;
+            }
+        }
+    }
+
+    void updateServiceStateArfcnRsrpBoost(ServiceState serviceState,
+            CellIdentity cellIdentity) {
+        int rsrpBoost = 0;
+        int arfcn;
+
+        synchronized (mRsrpBoostLock) {
+            switch (cellIdentity.getType()) {
+                case CellInfo.TYPE_LTE:
+                    arfcn = ((CellIdentityLte) cellIdentity).getEarfcn();
+                    if (arfcn != INVALID_ARFCN
+                            && containsEarfcnInEarfcnRange(mEarfcnPairListForRsrpBoost,
+                            arfcn) != -1) {
+                        rsrpBoost = mLteRsrpBoost;
+                    }
+                    break;
+                case CellInfo.TYPE_NR:
+                    arfcn = ((CellIdentityNr) cellIdentity).getNrarfcn();
+                    if (arfcn != INVALID_ARFCN) {
+                        int index = containsEarfcnInEarfcnRange(mNrarfcnRangeListForRsrpBoost,
+                                arfcn);
+                        if (index != -1) {
+                            rsrpBoost = mNrRsrpBoost[index];
+                        }
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+        serviceState.setArfcnRsrpBoost(rsrpBoost);
+    }
+
+    /**
+     * Checks if the provided earfcn falls within the range of earfcns.
+     *
+     * return int index in earfcnPairList if earfcn falls within the provided range; -1 otherwise.
+     */
+    private static int containsEarfcnInEarfcnRange(ArrayList<Pair<Integer, Integer>> earfcnPairList,
+            int earfcn) {
+        int index = 0;
+        if (earfcnPairList != null) {
+            for (Pair<Integer, Integer> earfcnPair : earfcnPairList) {
+                if ((earfcn >= earfcnPair.first) && (earfcn <= earfcnPair.second)) {
+                    return index;
+                }
+                index++;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Convert the earfcnStringArray to list of pairs.
+     *
+     * Format of the earfcnsList is expected to be {"erafcn1_start-earfcn1_end",
+     * "earfcn2_start-earfcn2_end" ... }
+     */
+    private static ArrayList<Pair<Integer, Integer>> convertEarfcnStringArrayToPairList(
+            String[] earfcnsList) {
+        ArrayList<Pair<Integer, Integer>> earfcnPairList = new ArrayList<Pair<Integer, Integer>>();
+
+        if (earfcnsList != null) {
+            int earfcnStart;
+            int earfcnEnd;
+            for (int i = 0; i < earfcnsList.length; i++) {
+                try {
+                    String[] earfcns = earfcnsList[i].split("-");
+                    if (earfcns.length != 2) {
+                        if (DBG) {
+                            log("Invalid earfcn range format");
+                        }
+                        return null;
+                    }
+
+                    earfcnStart = Integer.parseInt(earfcns[0]);
+                    earfcnEnd = Integer.parseInt(earfcns[1]);
+
+                    if (earfcnStart > earfcnEnd) {
+                        if (DBG) {
+                            log("Invalid earfcn range format");
+                        }
+                        return null;
+                    }
+
+                    earfcnPairList.add(new Pair<Integer, Integer>(earfcnStart, earfcnEnd));
+                } catch (PatternSyntaxException pse) {
+                    if (DBG) {
+                        log("Invalid earfcn range format");
+                    }
+                    return null;
+                } catch (NumberFormatException nfe) {
+                    if (DBG) {
+                        log("Invalid earfcn number format");
+                    }
+                    return null;
+                }
+            }
+        }
+
+        return earfcnPairList;
     }
 
     /**
@@ -486,85 +933,11 @@
         };
     }
 
-    private static boolean isRanAndSignalMeasurementTypeMatch(
-            @AccessNetworkConstants.RadioAccessNetworkType int ran,
-            @SignalThresholdInfo.SignalMeasurementType int measurement,
-            SignalThresholdInfo info) {
-        return ran == info.getRadioAccessNetworkType()
-                && measurement == info.getSignalMeasurementType();
-    }
-
-    private static boolean isSignalReportRequestedWhileIdle(SignalStrengthUpdateRequest request) {
-        return request.isSystemThresholdReportingRequestedWhileIdle()
-                || request.isReportingRequestedWhileIdle();
-    }
-
-    /**
-     * Gets the carrier configuration values for a particular subscription.
-     *
-     * @return A {@link PersistableBundle} containing the config for the given subId,
-     *         or default values for an invalid subId.
-     */
-    @NonNull
-    private PersistableBundle getCarrierConfig() {
-        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager != null) {
-            // If an invalid subId is used, this bundle will contain default values.
-            PersistableBundle config = configManager.getConfigForSubId(mPhone.getSubId());
-            if (config != null) {
-                return config;
-            }
-        }
-        // Return static default defined in CarrierConfigManager.
-        return CarrierConfigManager.getDefaultConfig();
-    }
-
-    private class SignalRequestRecord implements IBinder.DeathRecipient {
-        final int mSubId; // subId the request originally applied to
-        final int mCallingUid;
-        final SignalStrengthUpdateRequest mRequest;
-
-        SignalRequestRecord(int subId, int uid, @NonNull SignalStrengthUpdateRequest request) {
-            this.mCallingUid = uid;
-            this.mSubId = subId;
-            this.mRequest = request;
-        }
-
-        @Override
-        public void binderDied() {
-            clearSignalStrengthUpdateRequest(mSubId, mCallingUid, mRequest, null /*onCompleted*/);
-        }
-
-        @Override
-        public String toString() {
-            StringBuffer sb = new StringBuffer("SignalRequestRecord {");
-            sb.append("mSubId=").append(mSubId);
-            sb.append(" mCallingUid=").append(mCallingUid);
-            sb.append(" mRequest=").append(mRequest).append("}");
-            return sb.toString();
-        }
-    }
-
-    private void updateAlwaysReportSignalStrength() {
-        final int curSubId = mPhone.getSubId();
-        boolean alwaysReport = mSignalRequestRecords.stream().anyMatch(
-                srr -> srr.mSubId == curSubId && isSignalReportRequestedWhileIdle(srr.mRequest));
-
-        // TODO(b/177924721): TM#setAlwaysReportSignalStrength will be removed and we will not
-        // worry about unset flag which was set by other client.
-        mPhone.setAlwaysReportSignalStrength(alwaysReport);
-    }
-
-    /**
-     * Called when RIL is connected during boot up or after modem restart. Set the default criteria
-     * so that modem can start with default state before updated criteria is ready.
-     */
-    private void onReset() {
-        setDefaultSignalStrengthReportingCriteria();
-    }
-
-    private void log(String msg) {
+    private static void log(String msg) {
         if (DBG) Rlog.d(TAG, msg);
     }
+
+    private static void loge(String msg) {
+        Rlog.e(TAG, msg);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 36751a3..d05ef12 100644
--- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -597,6 +597,10 @@
     }
 
     private void updateCarrierServices(int phoneId, String simState) {
+        if (!SubscriptionManager.isValidPhoneId(phoneId)) {
+            logd("Ignore updateCarrierServices request with invalid phoneId " + phoneId);
+            return;
+        }
         CarrierConfigManager configManager =
                 (CarrierConfigManager) sContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
         configManager.updateConfigForPhoneId(phoneId, simState);
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 631bbdd..85d3974 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -1660,6 +1660,7 @@
             if (!uplinkUpdated) {
                 mUplinkBandwidth = values.second;
             }
+            mUplinkBandwidth = Math.min(mUplinkBandwidth, mDownlinkBandwidth);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/LinkBandwidthEstimator.java b/src/java/com/android/internal/telephony/dataconnection/LinkBandwidthEstimator.java
index 833cf86..3ab6ddf 100644
--- a/src/java/com/android/internal/telephony/dataconnection/LinkBandwidthEstimator.java
+++ b/src/java/com/android/internal/telephony/dataconnection/LinkBandwidthEstimator.java
@@ -133,6 +133,7 @@
     // Used to derive byte count threshold from avg BW
     private static final int LOW_BW_TO_AVG_BW_RATIO_NUM = 3;
     private static final int LOW_BW_TO_AVG_BW_RATIO_DEN = 8;
+    private static final int MAX_BW_TO_STATIC_BW_RATIO = 15;
     private static final int BYTE_DELTA_THRESHOLD_MIN_KB = 10;
     private static final int MAX_ERROR_PERCENT = 100 * 100;
     private static final String[] AVG_BW_PER_RAT = {
@@ -606,7 +607,8 @@
                 return;
             }
             long linkBandwidthLongKbps = bytesDelta * 8 / timeDeltaMs * 1000 / 1024;
-            if (linkBandwidthLongKbps > Integer.MAX_VALUE || linkBandwidthLongKbps < 0) {
+            if (linkBandwidthLongKbps > (long) mStaticBwKbps * MAX_BW_TO_STATIC_BW_RATIO
+                    || linkBandwidthLongKbps < 0) {
                 return;
             }
             int linkBandwidthKbps = (int) linkBandwidthLongKbps;
@@ -691,6 +693,45 @@
             return -1;
         }
 
+        private int getAvgUsedBandwidthAdjacentThreeLevelKbps() {
+            String dataRatName = getDataRatName(mDataRat);
+            NetworkBandwidth network = lookupNetwork(mPlmn, dataRatName);
+
+            int bandwidthAtLow = getAvgUsedBandwidthAtLevel(network, mSignalLevel - 1);
+            int bandwidthAtHigh = getAvgUsedBandwidthAtLevel(network, mSignalLevel + 1);
+            if (bandwidthAtLow > 0 && bandwidthAtHigh > 0) {
+                return (bandwidthAtLow + bandwidthAtHigh) / 2;
+            }
+
+            int count = 0;
+            long value = 0;
+            for (int i = -1; i <= 1; i++) {
+                int currLevel = mSignalLevel + i;
+                if (currLevel < 0 || currLevel >= NUM_SIGNAL_LEVEL) {
+                    continue;
+                }
+                count += network.getCount(mLink, currLevel);
+                value += network.getValue(mLink, currLevel);
+            }
+
+            if (count >= BW_STATS_COUNT_THRESHOLD) {
+                return (int) (value / count);
+            }
+            return -1;
+        }
+
+        private int getAvgUsedBandwidthAtLevel(NetworkBandwidth network,
+                int signalLevel) {
+            if (signalLevel < 0 || signalLevel >= NUM_SIGNAL_LEVEL) {
+                return -1;
+            }
+            int count = network.getCount(mLink, signalLevel);
+            if (count >= BW_STATS_COUNT_THRESHOLD) {
+                return (int) (network.getValue(mLink, signalLevel) / count);
+            }
+            return -1;
+        }
+
         private int getCurrentCount() {
             String dataRatName = getDataRatName(mDataRat);
             NetworkBandwidth network = lookupNetwork(mPlmn, dataRatName);
@@ -703,6 +744,10 @@
             if (mAvgUsedKbps > 0) {
                 return mAvgUsedKbps;
             }
+            mAvgUsedKbps = getAvgUsedBandwidthAdjacentThreeLevelKbps();
+            if (mAvgUsedKbps > 0) {
+                return mAvgUsedKbps;
+            }
             // Fall back to static value
             return mStaticBwKbps;
         }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
index 3ee7dba..0745d93 100755
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -59,6 +59,8 @@
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.telephony.Rlog;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Objects;
 import java.util.Set;
@@ -1318,7 +1320,11 @@
             return;
         }
         if (extras.containsKey(ImsCallProfile.EXTRA_FORWARDED_NUMBER)) {
-            mForwardedNumber = extras.getStringArrayList(ImsCallProfile.EXTRA_FORWARDED_NUMBER);
+            String[] forwardedNumberArray =
+                    extras.getStringArray(ImsCallProfile.EXTRA_FORWARDED_NUMBER);
+            if (forwardedNumberArray != null) {
+                mForwardedNumber = new ArrayList<String>(Arrays.asList(forwardedNumberArray));
+            }
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/metrics/CallQualityMetrics.java b/src/java/com/android/internal/telephony/metrics/CallQualityMetrics.java
index cf98acb..208aff6 100644
--- a/src/java/com/android/internal/telephony/metrics/CallQualityMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/CallQualityMetrics.java
@@ -25,7 +25,7 @@
 import android.util.Pair;
 
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.SignalStrengthController;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
@@ -215,13 +215,13 @@
 
     // Returns the LTE signal to noise ratio, or 0 if unavailable
     private Integer getLteSnr() {
-        ServiceStateTracker sst = mPhone.getDefaultPhone().getServiceStateTracker();
-        if (sst == null) {
-            Rlog.e(TAG, "getLteSnr: unable to get SST for phone " + mPhone.getPhoneId());
+        SignalStrengthController ssc = mPhone.getDefaultPhone().getSignalStrengthController();
+        if (ssc == null) {
+            Rlog.e(TAG, "getLteSnr: unable to get SSC for phone " + mPhone.getPhoneId());
             return CellInfo.UNAVAILABLE;
         }
 
-        SignalStrength ss = sst.getSignalStrength();
+        SignalStrength ss = ssc.getSignalStrength();
         if (ss == null) {
             Rlog.e(TAG, "getLteSnr: unable to get SignalStrength for phone " + mPhone.getPhoneId());
             return CellInfo.UNAVAILABLE;
diff --git a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
index d2f740c..be3c492 100644
--- a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
@@ -415,6 +415,12 @@
             // internal fields for tracking
             proto.setupBeginMillis = getTimeMillis();
 
+            // audio codec might have already been set
+            int codec = audioQualityToCodec(bearer, conn.getAudioCodec());
+            if (codec != AudioCodec.AUDIO_CODEC_UNKNOWN) {
+                proto.codecBitmask = (1L << codec);
+            }
+
             proto.concurrentCallCountAtStart = mCallProtos.size();
             mCallProtos.put(id, proto);
 
diff --git a/src/java/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactory.java b/src/java/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactory.java
index aa72722..965cd41 100644
--- a/src/java/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactory.java
+++ b/src/java/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactory.java
@@ -21,10 +21,10 @@
 import android.content.Context;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
-import android.os.TimestampedValue;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzSignal;
 import com.android.internal.telephony.NitzStateMachine.DeviceState;
 import com.android.internal.telephony.nitz.NitzStateMachineImpl.NitzSignalInputFilterPredicate;
 import com.android.telephony.Rlog;
@@ -84,8 +84,8 @@
          */
         @Nullable
         Boolean mustProcessNitzSignal(
-                @Nullable TimestampedValue<NitzData> previousSignal,
-                @NonNull TimestampedValue<NitzData> newSignal);
+                @Nullable NitzSignal previousSignal,
+                @NonNull NitzSignal newSignal);
     }
 
     /**
@@ -132,8 +132,9 @@
                 // Acquire the wake lock as we are reading the elapsed realtime clock below.
                 wakeLock.acquire();
 
-                long elapsedRealtime = deviceState.elapsedRealtime();
-                long millisSinceNitzReceived = elapsedRealtime - newSignal.getReferenceTimeMillis();
+                long elapsedRealtime = deviceState.elapsedRealtimeMillis();
+                long millisSinceNitzReceived =
+                        elapsedRealtime - newSignal.getReceiptElapsedRealtimeMillis();
                 if (millisSinceNitzReceived < 0 || millisSinceNitzReceived > Integer.MAX_VALUE) {
                     if (DBG) {
                         Rlog.d(LOG_TAG, "mustProcessNitzSignal: Not processing NITZ signal"
@@ -178,15 +179,15 @@
             @Override
             @NonNull
             public Boolean mustProcessNitzSignal(
-                    @NonNull TimestampedValue<NitzData> previousSignal,
-                    @NonNull TimestampedValue<NitzData> newSignal) {
+                    @NonNull NitzSignal previousSignal,
+                    @NonNull NitzSignal newSignal) {
                 Objects.requireNonNull(newSignal);
-                Objects.requireNonNull(newSignal.getValue());
+                Objects.requireNonNull(newSignal.getNitzData());
                 Objects.requireNonNull(previousSignal);
-                Objects.requireNonNull(previousSignal.getValue());
+                Objects.requireNonNull(previousSignal.getNitzData());
 
-                NitzData newNitzData = newSignal.getValue();
-                NitzData previousNitzData = previousSignal.getValue();
+                NitzData newNitzData = newSignal.getNitzData();
+                NitzData previousNitzData = previousSignal.getNitzData();
 
                 // Compare the discrete NitzData fields associated with local time offset. Any
                 // difference and we should process the signal regardless of how recent the last one
@@ -195,26 +196,36 @@
                     return true;
                 }
 
-                // Now check the continuous NitzData field (time) to see if it is sufficiently
-                // different.
+                // Check the time-related NitzData fields to see if they are sufficiently different.
+
+                // See if the NITZ signals have been received sufficiently far apart. If yes, we
+                // want to process the new one.
                 int nitzUpdateSpacing = deviceState.getNitzUpdateSpacingMillis();
+                long elapsedRealtimeSinceLastSaved = newSignal.getReceiptElapsedRealtimeMillis()
+                        - previousSignal.getReceiptElapsedRealtimeMillis();
+                if (elapsedRealtimeSinceLastSaved > nitzUpdateSpacing) {
+                    return true;
+                }
+
+                // See if the NITZ signals have sufficiently different encoded UTC times. If yes,
+                // then we want to process the new one.
                 int nitzUpdateDiff = deviceState.getNitzUpdateDiffMillis();
 
-                // Calculate the elapsed time between the new signal and the last signal.
-                long elapsedRealtimeSinceLastSaved = newSignal.getReferenceTimeMillis()
-                        - previousSignal.getReferenceTimeMillis();
-
-                // Calculate the UTC difference between the time the two signals hold.
+                // Calculate the UTC difference between the time the two signals hold, accounting
+                // for any difference in receipt time and age.
                 long utcTimeDifferenceMillis = newNitzData.getCurrentTimeInMillis()
                         - previousNitzData.getCurrentTimeInMillis();
+                long ageAdjustedElapsedRealtimeDifferenceMillis =
+                        newSignal.getAgeAdjustedElapsedRealtimeMillis()
+                                - previousSignal.getAgeAdjustedElapsedRealtimeMillis();
 
-                // Ideally the difference between elapsedRealtimeSinceLastSaved and
-                // utcTimeDifferenceMillis would be zero.
-                long millisGainedOrLost = Math
-                        .abs(utcTimeDifferenceMillis - elapsedRealtimeSinceLastSaved);
-
-                if (elapsedRealtimeSinceLastSaved > nitzUpdateSpacing
-                        || millisGainedOrLost > nitzUpdateDiff) {
+                // In ideal conditions, the difference between
+                // ageAdjustedElapsedRealtimeSinceLastSaved and utcTimeDifferenceMillis will be zero
+                // if two NITZ signals are consistent and if the elapsed realtime clock is ticking
+                // at the correct rate.
+                long millisGainedOrLost = Math.abs(
+                        utcTimeDifferenceMillis - ageAdjustedElapsedRealtimeDifferenceMillis);
+                if (millisGainedOrLost > nitzUpdateDiff) {
                     return true;
                 }
 
@@ -256,8 +267,8 @@
         }
 
         @Override
-        public boolean mustProcessNitzSignal(@Nullable TimestampedValue<NitzData> oldSignal,
-                @NonNull TimestampedValue<NitzData> newSignal) {
+        public boolean mustProcessNitzSignal(@Nullable NitzSignal oldSignal,
+                @NonNull NitzSignal newSignal) {
             Objects.requireNonNull(newSignal);
 
             for (TrivalentPredicate component : mComponents) {
diff --git a/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java b/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java
index a36eb4f..7a53491 100644
--- a/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java
+++ b/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java
@@ -25,6 +25,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzSignal;
 import com.android.internal.telephony.NitzStateMachine;
 import com.android.internal.telephony.Phone;
 import com.android.internal.util.IndentingPrintWriter;
@@ -68,8 +69,8 @@
          * See {@link NitzSignalInputFilterPredicate}.
          */
         boolean mustProcessNitzSignal(
-                @Nullable TimestampedValue<NitzData> oldSignal,
-                @NonNull TimestampedValue<NitzData> newSignal);
+                @Nullable NitzSignal oldSignal,
+                @NonNull NitzSignal newSignal);
     }
 
     /**
@@ -89,7 +90,7 @@
         @NonNull
         TelephonyTimeZoneSuggestion getTimeZoneSuggestion(
                 int slotIndex, @Nullable String countryIsoCode,
-                @Nullable TimestampedValue<NitzData> nitzSignal);
+                @Nullable NitzSignal nitzSignal);
     }
 
     static final String LOG_TAG = "NewNitzStateMachineImpl";
@@ -114,7 +115,7 @@
      * needs to be recalculated when something else has changed.
      */
     @Nullable
-    private TimestampedValue<NitzData> mLatestNitzSignal;
+    private NitzSignal mLatestNitzSignal;
 
     // Time Zone detection state.
 
@@ -227,14 +228,14 @@
     }
 
     @Override
-    public void handleNitzReceived(@NonNull TimestampedValue<NitzData> nitzSignal) {
+    public void handleNitzReceived(@NonNull NitzSignal nitzSignal) {
         if (DBG) {
             Rlog.d(LOG_TAG, "handleNitzReceived: nitzSignal=" + nitzSignal);
         }
         Objects.requireNonNull(nitzSignal);
 
         // Perform input filtering to filter bad data and avoid processing signals too often.
-        TimestampedValue<NitzData> previousNitzSignal = mLatestNitzSignal;
+        NitzSignal previousNitzSignal = mLatestNitzSignal;
         if (!mNitzSignalInputFilter.mustProcessNitzSignal(previousNitzSignal, nitzSignal)) {
             return;
         }
@@ -278,7 +279,8 @@
      * Perform a round of time zone detection and notify the time zone detection service as needed.
      */
     private void doTimeZoneDetection(
-            @Nullable String countryIsoCode, @Nullable TimestampedValue<NitzData> nitzSignal,
+            @Nullable String countryIsoCode, @Nullable NitzSignal
+            nitzSignal,
             @NonNull String reason) {
         try {
             Objects.requireNonNull(reason);
@@ -306,7 +308,7 @@
     /**
      * Perform a round of time detection and notify the time detection service as needed.
      */
-    private void doTimeDetection(@Nullable TimestampedValue<NitzData> nitzSignal,
+    private void doTimeDetection(@Nullable NitzSignal nitzSignal,
             @NonNull String reason) {
         try {
             Objects.requireNonNull(reason);
@@ -317,9 +319,7 @@
                 builder.addDebugInfo("Clearing time suggestion"
                         + " reason=" + reason);
             } else {
-                TimestampedValue<Long> newNitzTime = new TimestampedValue<>(
-                        nitzSignal.getReferenceTimeMillis(),
-                        nitzSignal.getValue().getCurrentTimeInMillis());
+                TimestampedValue<Long> newNitzTime = nitzSignal.createTimeSignal();
                 builder.setUtcTime(newNitzTime);
                 builder.addDebugInfo("Sending new time suggestion"
                         + " nitzSignal=" + nitzSignal
@@ -350,6 +350,6 @@
 
     @Nullable
     public NitzData getCachedNitzData() {
-        return mLatestNitzSignal != null ? mLatestNitzSignal.getValue() : null;
+        return mLatestNitzSignal != null ? mLatestNitzSignal.getNitzData() : null;
     }
 }
diff --git a/src/java/com/android/internal/telephony/nitz/TimeZoneSuggesterImpl.java b/src/java/com/android/internal/telephony/nitz/TimeZoneSuggesterImpl.java
index 48491df..342705b 100644
--- a/src/java/com/android/internal/telephony/nitz/TimeZoneSuggesterImpl.java
+++ b/src/java/com/android/internal/telephony/nitz/TimeZoneSuggesterImpl.java
@@ -21,12 +21,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.os.TimestampedValue;
 import android.text.TextUtils;
 import android.timezone.CountryTimeZones.OffsetResult;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzSignal;
 import com.android.internal.telephony.NitzStateMachine.DeviceState;
 import com.android.internal.telephony.nitz.NitzStateMachineImpl.TimeZoneSuggester;
 import com.android.internal.telephony.nitz.TimeZoneLookupHelper.CountryResult;
@@ -55,12 +55,12 @@
     @Override
     @NonNull
     public TelephonyTimeZoneSuggestion getTimeZoneSuggestion(int slotIndex,
-            @Nullable String countryIsoCode, @Nullable TimestampedValue<NitzData> nitzSignal) {
+            @Nullable String countryIsoCode, @Nullable NitzSignal nitzSignal) {
         try {
             // Check for overriding NITZ-based signals from Android running in an emulator.
             TelephonyTimeZoneSuggestion overridingSuggestion = null;
             if (nitzSignal != null) {
-                NitzData nitzData = nitzSignal.getValue();
+                NitzData nitzData = nitzSignal.getNitzData();
                 if (nitzData.getEmulatorHostTimeZone() != null) {
                     TelephonyTimeZoneSuggestion.Builder builder =
                             new TelephonyTimeZoneSuggestion.Builder(slotIndex)
@@ -135,9 +135,9 @@
      */
     @NonNull
     private TelephonyTimeZoneSuggestion findTimeZoneForTestNetwork(
-            int slotIndex, @NonNull TimestampedValue<NitzData> nitzSignal) {
+            int slotIndex, @NonNull NitzSignal nitzSignal) {
         Objects.requireNonNull(nitzSignal);
-        NitzData nitzData = Objects.requireNonNull(nitzSignal.getValue());
+        NitzData nitzData = Objects.requireNonNull(nitzSignal.getNitzData());
 
         TelephonyTimeZoneSuggestion.Builder suggestionBuilder =
                 new TelephonyTimeZoneSuggestion.Builder(slotIndex);
@@ -166,7 +166,7 @@
     @NonNull
     private TelephonyTimeZoneSuggestion findTimeZoneFromCountryAndNitz(
             int slotIndex, @NonNull String countryIsoCode,
-            @NonNull TimestampedValue<NitzData> nitzSignal) {
+            @NonNull NitzSignal nitzSignal) {
         Objects.requireNonNull(countryIsoCode);
         Objects.requireNonNull(nitzSignal);
 
@@ -175,7 +175,7 @@
         suggestionBuilder.addDebugInfo("findTimeZoneFromCountryAndNitz:"
                 + " countryIsoCode=" + countryIsoCode
                 + ", nitzSignal=" + nitzSignal);
-        NitzData nitzData = Objects.requireNonNull(nitzSignal.getValue());
+        NitzData nitzData = Objects.requireNonNull(nitzSignal.getNitzData());
         if (isNitzSignalOffsetInfoBogus(countryIsoCode, nitzData)) {
             suggestionBuilder.addDebugInfo(
                     "findTimeZoneFromCountryAndNitz: NITZ signal looks bogus");
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
index be23df8..ac5facd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
@@ -114,6 +114,7 @@
                 mPhone).getCachedAllowedNetworkTypesBitmask();
         doReturn(false).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
+        doReturn(new int[] {0}).when(mServiceState).getCellBandwidths();
         mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
         processAllMessages();
     }
@@ -1186,4 +1187,49 @@
         }
         doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList();
     }
+
+    @Test
+    public void testTransitionToCurrentStateNrConnectedWithLowBandwidth() 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();
+        doReturn(new int[] {19999}).when(mServiceState).getCellBandwidths();
+        mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
+        broadcastCarrierConfigs();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("connected", getCurrentState().getName());
+    }
+
+    @Test
+    public void testTransitionToCurrentStateNrConnectedWithHighBandwidth() 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();
+        doReturn(new int[] {20001}).when(mServiceState).getCellBandwidths();
+        mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
+        broadcastCarrierConfigs();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("connected_mmwave", getCurrentState().getName());
+    }
+
+    @Test
+    public void testNrAdvancedDisabledWhileRoaming() throws Exception {
+        assertEquals("DefaultState", getCurrentState().getName());
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        doReturn(true).when(mServiceState).getDataRoaming();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
+        mBundle.putBoolean(CarrierConfigManager.KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL, false);
+        broadcastCarrierConfigs();
+
+        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        processAllMessages();
+        assertEquals("connected", getCurrentState().getName());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NitzSignalTest.java b/tests/telephonytests/src/com/android/internal/telephony/NitzSignalTest.java
new file mode 100644
index 0000000..352a9aa
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/NitzSignalTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2021 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.junit.Assert.assertNotEquals;
+
+import org.junit.Test;
+
+public class NitzSignalTest {
+
+    /** Sample cases for equals() and hashCode(). Not exhaustive. */
+    @Test
+    public void testEqualsAndHashCode() {
+        long receiptElapsedMillis1 = 1111;
+        NitzData nitzData1 = NitzData.createForTests(0, 0, 1234, null);
+        long ageMillis1 = 11;
+        NitzSignal nitzSignal1 = new NitzSignal(receiptElapsedMillis1, nitzData1, ageMillis1);
+        assertEquals(nitzSignal1, nitzSignal1);
+        assertEquals(nitzSignal1.hashCode(), nitzSignal1.hashCode());
+
+        NitzSignal nitzSignal1v2 = new NitzSignal(receiptElapsedMillis1, nitzData1, ageMillis1);
+        assertEquals(nitzSignal1, nitzSignal1v2);
+        assertEquals(nitzSignal1v2, nitzSignal1);
+        assertEquals(nitzSignal1.hashCode(), nitzSignal1v2.hashCode());
+
+        long receiptElapsedMillis2 = 2222;
+        NitzData nitzData2 = NitzData.createForTests(0, 0, 2345, null);
+        long ageMillis2 = 11;
+        NitzSignal nitzSignal2 = new NitzSignal(receiptElapsedMillis2, nitzData2, ageMillis2);
+        assertNotEquals(nitzSignal1, nitzSignal2);
+        assertNotEquals(nitzSignal2, nitzSignal1);
+    }
+
+    @Test
+    public void testGetAgeAdjustedRealtimeMillis_zeroAge() {
+        NitzData nitzData = NitzData.createForTests(0, 0, 1234, null);
+        long receiptElapsedRealtimeMillis = 1111;
+        long ageMillis = 0;
+        NitzSignal nitzSignal =
+                new NitzSignal(receiptElapsedRealtimeMillis, nitzData, ageMillis);
+        assertEquals(receiptElapsedRealtimeMillis,
+                nitzSignal.getReceiptElapsedRealtimeMillis());
+        assertEquals(ageMillis, nitzSignal.getAgeMillis());
+        assertEquals(receiptElapsedRealtimeMillis - ageMillis,
+                nitzSignal.getAgeAdjustedElapsedRealtimeMillis());
+    }
+
+    @Test
+    public void testGetAgeAdjustedRealtimeMillis_withAge() {
+        NitzData nitzData = NitzData.createForTests(0, 0, 1234, null);
+        long receiptElapsedRealtimeMillis = 1111;
+        long ageMillis = 5000;
+        NitzSignal nitzSignal =
+                new NitzSignal(receiptElapsedRealtimeMillis, nitzData, ageMillis);
+        assertEquals(receiptElapsedRealtimeMillis,
+                nitzSignal.getReceiptElapsedRealtimeMillis());
+        assertEquals(ageMillis, nitzSignal.getAgeMillis());
+        assertEquals(receiptElapsedRealtimeMillis - ageMillis,
+                nitzSignal.getAgeAdjustedElapsedRealtimeMillis());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index a425b25..5cfff52 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -56,7 +56,6 @@
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.SystemClock;
-import android.os.TimestampedValue;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.telephony.AccessNetworkConstants;
@@ -70,13 +69,7 @@
 import android.telephony.CellIdentityWcdma;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoGsm;
-import android.telephony.CellSignalStrength;
-import android.telephony.CellSignalStrengthCdma;
 import android.telephony.CellSignalStrengthGsm;
-import android.telephony.CellSignalStrengthLte;
-import android.telephony.CellSignalStrengthNr;
-import android.telephony.CellSignalStrengthTdscdma;
-import android.telephony.CellSignalStrengthWcdma;
 import android.telephony.INetworkService;
 import android.telephony.LteVopsSupportInfo;
 import android.telephony.NetworkRegistrationInfo;
@@ -84,7 +77,6 @@
 import android.telephony.NrVopsSupportInfo;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -142,8 +134,7 @@
 
     // SST now delegates all signal strength operations to SSC
     // Add Mock SSC as the dependency to avoid NPE
-    @Mock
-    private SignalStrengthController mSignalStrengthController;
+    private SignalStrengthController mSsc;
 
     private ServiceStateTracker sst;
     private ServiceStateTrackerTestHandler mSSTTestHandler;
@@ -194,8 +185,8 @@
 
         @Override
         public void onLooperPrepared() {
-            mSignalStrengthController = new SignalStrengthController(mPhone);
-            doReturn(mSignalStrengthController).when(mPhone).getSignalStrengthController();
+            mSsc = new SignalStrengthController(mPhone);
+            doReturn(mSsc).when(mPhone).getSignalStrengthController();
 
             sst = new ServiceStateTracker(mPhone, mSimulatedCommands);
             sst.setServiceStateStats(mServiceStateStats);
@@ -705,58 +696,6 @@
         verify(mPhone, times(1)).notifyServiceStateChanged(any(ServiceState.class));
     }
 
-    private void sendSignalStrength(SignalStrength ss) {
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-    }
-
-    @Test
-    @MediumTest
-    public void testSignalStrength() {
-        // Send in GSM Signal Strength Info and expect isGsm == true
-        SignalStrength ss = new SignalStrength(
-                new CellSignalStrengthCdma(),
-                new CellSignalStrengthGsm(-53, 0, SignalStrength.INVALID),
-                new CellSignalStrengthWcdma(),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(),
-                new CellSignalStrengthNr());
-
-        sendSignalStrength(ss);
-        assertEquals(sst.getSignalStrength(), ss);
-        assertEquals(sst.getSignalStrength().isGsm(), true);
-
-        // Send in CDMA+LTE Signal Strength Info and expect isGsm == true
-        ss = new SignalStrength(
-                new CellSignalStrengthCdma(-90, -12,
-                        SignalStrength.INVALID, SignalStrength.INVALID, SignalStrength.INVALID),
-                new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(
-                        -110, -114, -5, 0, SignalStrength.INVALID, SignalStrength.INVALID),
-                new CellSignalStrengthNr());
-
-        sendSignalStrength(ss);
-        assertEquals(sst.getSignalStrength(), ss);
-        assertEquals(sst.getSignalStrength().isGsm(), true);
-
-        // Send in CDMA-only Signal Strength Info and expect isGsm == false
-        ss = new SignalStrength(
-                new CellSignalStrengthCdma(-90, -12,
-                        SignalStrength.INVALID, SignalStrength.INVALID, SignalStrength.INVALID),
-                new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(),
-                new CellSignalStrengthNr());
-
-        sendSignalStrength(ss);
-        assertEquals(sst.getSignalStrength(), ss);
-        assertEquals(sst.getSignalStrength().isGsm(), false);
-    }
-
     private void sendCarrierConfigUpdate() {
         CarrierConfigManager mockConfigManager = Mockito.mock(CarrierConfigManager.class);
         when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
@@ -770,182 +709,6 @@
     }
 
     @Test
-    public void testLteSignalStrengthReportingCriteria() {
-        SignalStrength ss = new SignalStrength(
-                new CellSignalStrengthCdma(),
-                new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(
-                        -110, /* rssi */
-                        -114, /* rsrp */
-                        -5, /* rsrq */
-                        0, /* rssnr */
-                        SignalStrength.INVALID, /* cqi */
-                        SignalStrength.INVALID /* ta */),
-                new CellSignalStrengthNr());
-
-        mBundle.putBoolean(CarrierConfigManager.KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL,
-                true);
-
-        sendCarrierConfigUpdate();
-
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        // Default thresholds are POOR=-115 MODERATE=-105 GOOD=-95 GREAT=-85
-        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR, sst.getSignalStrength().getLevel());
-
-        int[] lteThresholds = {
-                -130, // SIGNAL_STRENGTH_POOR
-                -120, // SIGNAL_STRENGTH_MODERATE
-                -110, // SIGNAL_STRENGTH_GOOD
-                -100,  // SIGNAL_STRENGTH_GREAT
-        };
-        mBundle.putIntArray(CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
-                lteThresholds);
-        sendCarrierConfigUpdate();
-
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        assertEquals(sst.getSignalStrength().getLevel(),
-                CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
-    }
-
-    @Test
-    public void test5gNrSignalStrengthReportingCriteria_UseSsRsrp() {
-        SignalStrength ss = new SignalStrength(
-                new CellSignalStrengthCdma(),
-                new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(),
-                new CellSignalStrengthNr(
-                    -139, /** csiRsrp NONE */
-                    -20, /** csiRsrq NONE */
-                    -23, /** CsiSinr NONE */
-                    -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
-                    -20, /** SsRsrq NONE */
-                    -23) /** SsSinr NONE */
-         );
-
-        // SSRSRP = 1 << 0
-        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
-                CellSignalStrengthNr.USE_SSRSRP);
-        sendCarrierConfigUpdate();
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_GREAT, sst.getSignalStrength().getLevel());
-    }
-
-    @Test
-    public void test5gNrSignalStrengthReportingCriteria_UseSsRsrpAndSsRsrq() {
-        SignalStrength ss = new SignalStrength(
-                new CellSignalStrengthCdma(),
-                new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(),
-                new CellSignalStrengthNr(
-                    -139, /** csiRsrp NONE */
-                    -20, /** csiRsrq NONE */
-                    -23, /** CsiSinr NONE */
-                    -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
-                    -32, /** SsRsrq NONE */
-                    -23) /** SsSinr NONE */
-        );
-
-        // SSRSRP = 1 << 0 | SSSINR = 1 << 2
-        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
-                CellSignalStrengthNr.USE_SSRSRP | CellSignalStrengthNr.USE_SSRSRQ);
-        sendCarrierConfigUpdate();
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
-                sst.getSignalStrength().getLevel());
-    }
-
-    @Test
-    public void test5gNrSignalStrengthReportingCriteria_ConfiguredThresholds() {
-        SignalStrength ss = new SignalStrength(
-                new CellSignalStrengthCdma(),
-                new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(),
-                new CellSignalStrengthNr(
-                    -139, /** csiRsrp NONE */
-                    -20, /** csiRsrq NONE */
-                    -23, /** CsiSinr NONE */
-                    -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
-                    -20, /** SsRsrq NONE */
-                    -23) /** SsSinr NONE */
-        );
-
-        // SSRSRP = 1 << 0
-        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
-                CellSignalStrengthNr.USE_SSRSRP);
-        sendCarrierConfigUpdate();
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_GREAT, sst.getSignalStrength().getLevel());
-
-        int[] nrSsRsrpThresholds = {
-                -45, // SIGNAL_STRENGTH_POOR
-                -40, // SIGNAL_STRENGTH_MODERATE
-                -37, // SIGNAL_STRENGTH_GOOD
-                -34,  // SIGNAL_STRENGTH_GREAT
-        };
-        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY,
-                nrSsRsrpThresholds);
-        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
-                CellSignalStrengthNr.USE_SSRSRP);
-        sendCarrierConfigUpdate();
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR,
-                sst.getSignalStrength().getLevel());
-    }
-
-    @Test
-    public void testWcdmaSignalStrengthReportingCriteria() {
-        SignalStrength ss = new SignalStrength(
-                new CellSignalStrengthCdma(),
-                new CellSignalStrengthGsm(),
-                new CellSignalStrengthWcdma(-79, 0, -85, -5),
-                new CellSignalStrengthTdscdma(),
-                new CellSignalStrengthLte(),
-                new CellSignalStrengthNr());
-
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        assertEquals(sst.getSignalStrength().getLevel(), CellSignalStrength.SIGNAL_STRENGTH_GOOD);
-
-        int[] wcdmaThresholds = {
-                -110, // SIGNAL_STRENGTH_POOR
-                -100, // SIGNAL_STRENGTH_MODERATE
-                -90, // SIGNAL_STRENGTH_GOOD
-                -80  // SIGNAL_STRENGTH_GREAT
-        };
-        mBundle.putIntArray(CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY,
-                wcdmaThresholds);
-        mBundle.putString(
-                CarrierConfigManager.KEY_WCDMA_DEFAULT_SIGNAL_STRENGTH_MEASUREMENT_STRING,
-                "rscp");
-        sendCarrierConfigUpdate();
-        mSimulatedCommands.setSignalStrength(ss);
-        mSimulatedCommands.notifySignalStrength();
-        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-        assertEquals(sst.getSignalStrength().getLevel(), CellSignalStrength.SIGNAL_STRENGTH_GOOD);
-    }
-
-    @Test
     @MediumTest
     // TODO(nharold): we probably should remove support for this procedure (GET_LOC)
     public void testGsmCellLocation() {
@@ -2047,7 +1810,7 @@
 
     @Test
     @SmallTest
-    public void testSetTimeFromNITZStr() throws Exception {
+    public void testSetTimeFromNITZStr_withoutAge() throws Exception {
         {
             // Mock sending incorrect nitz str from RIL
             mSimulatedCommands.triggerNITZupdate("38/06/20,00:00:00+0");
@@ -2055,21 +1818,55 @@
             verify(mNitzStateMachine, times(0)).handleNitzReceived(any());
         }
         {
-            // Mock sending correct nitz str from RIL
+            // Mock sending correct nitz str from RIL with a zero ageMs
             String nitzStr = "15/06/20,00:00:00+0";
             NitzData expectedNitzData = NitzData.parse(nitzStr);
             mSimulatedCommands.triggerNITZupdate(nitzStr);
             waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
-            ArgumentCaptor<TimestampedValue<NitzData>> argumentsCaptor =
-                    ArgumentCaptor.forClass(TimestampedValue.class);
+            ArgumentCaptor<NitzSignal> argumentsCaptor =
+                    ArgumentCaptor.forClass(NitzSignal.class);
             verify(mNitzStateMachine, times(1))
                     .handleNitzReceived(argumentsCaptor.capture());
 
             // Confirm the argument was what we expected.
-            TimestampedValue<NitzData> actualNitzSignal = argumentsCaptor.getValue();
-            assertEquals(expectedNitzData, actualNitzSignal.getValue());
-            assertTrue(actualNitzSignal.getReferenceTimeMillis() <= SystemClock.elapsedRealtime());
+            NitzSignal actualNitzSignal = argumentsCaptor.getValue();
+            assertEquals(expectedNitzData, actualNitzSignal.getNitzData());
+            assertTrue(actualNitzSignal.getReceiptElapsedRealtimeMillis()
+                    <= SystemClock.elapsedRealtime());
+            assertEquals(actualNitzSignal.getAgeMillis(), 0);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testSetTimeFromNITZStr_withAge() throws Exception {
+        {
+            // Mock sending incorrect nitz str from RIL with a non-zero ageMs
+            long ageMs = 60 * 1000;
+            mSimulatedCommands.triggerNITZupdate("38/06/20,00:00:00+0", ageMs);
+            waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+            verify(mNitzStateMachine, times(0)).handleNitzReceived(any());
+        }
+        {
+            // Mock sending correct nitz str from RIL with a non-zero ageMs
+            String nitzStr = "21/08/15,00:00:00+0";
+            long ageMs = 60 * 1000;
+            NitzData expectedNitzData = NitzData.parse(nitzStr);
+            mSimulatedCommands.triggerNITZupdate(nitzStr, ageMs);
+            waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+
+            ArgumentCaptor<NitzSignal> argumentsCaptor =
+                    ArgumentCaptor.forClass(NitzSignal.class);
+            verify(mNitzStateMachine, times(1))
+                    .handleNitzReceived(argumentsCaptor.capture());
+
+            // Confirm the argument was what we expected.
+            NitzSignal actualNitzSignal = argumentsCaptor.getValue();
+            assertEquals(expectedNitzData, actualNitzSignal.getNitzData());
+            assertTrue(actualNitzSignal.getReceiptElapsedRealtimeMillis()
+                    <= SystemClock.elapsedRealtime());
+            assertEquals(actualNitzSignal.getAgeMillis(), ageMs);
         }
     }
 
@@ -2526,6 +2323,7 @@
         doReturn(true).when(mPhone).isPhoneTypeCdmaLte();
         doReturn(CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_RUIM).when(mCdmaSSM)
                 .getCdmaSubscriptionSource();
+        doReturn(PHONE_ID).when(mPhone).getPhoneId();
 
         logd("Calling updatePhoneType");
         // switch to CDMA
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java
index 0f2fc95..1f22352 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java
@@ -21,22 +21,39 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.content.Intent;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.CarrierConfigManager;
+import android.telephony.CellSignalStrength;
+import android.telephony.CellSignalStrengthCdma;
+import android.telephony.CellSignalStrengthGsm;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthNr;
+import android.telephony.CellSignalStrengthTdscdma;
+import android.telephony.CellSignalStrengthWcdma;
+import android.telephony.SignalStrength;
 import android.telephony.SignalStrengthUpdateRequest;
 import android.telephony.SignalThresholdInfo;
+import android.test.suitebuilder.annotation.MediumTest;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -46,6 +63,7 @@
  * Unit test for {@link SignalStrengthUpdateRequest}.
  */
 @RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class SignalStrengthControllerTest extends TelephonyTest {
 
     private static final String TAG = "SignalStrengthControllerTest";
@@ -53,47 +71,60 @@
     private static final int ACTIVE_SUB_ID = 0;
     private static final int INVALID_SUB_ID = 1000;
     private static final int CALLING_UID = 12345;
+    private static final int PHONE_ID = 0;
+    private static final String HOME_PLMN = "310260";
+    private static final String PLMN1 = "480123";
+    private static final String PLMN2 = "586111";
+    private static final String HOME_PNN = "home pnn";
+    private static final String[] CARRIER_CONFIG_SPDI = new String[] {HOME_PLMN, PLMN2};
+    private static final String[] CARRIER_CONFIG_EHPLMN = new String[] {HOME_PLMN, PLMN1};
+    private static final String[] CARRIER_CONFIG_PNN = new String[] {
+            String.format("%s,%s", HOME_PNN, "short"), "f2,s2"
+    };
 
-    private SignalStrengthController mSignalStrengthController;
-    private TestHandlerThread mTestHandlerThread;
+    @Mock
     private Handler mHandler;
 
-    private class TestHandlerThread extends HandlerThread {
-        private TestHandlerThread(String name) {
-            super(name);
-        }
-
-        @Override
-        protected void onLooperPrepared() {
-            mSignalStrengthController = new SignalStrengthController(mPhone);
-            when(mPhone.getSignalStrengthController()).thenReturn(mSignalStrengthController);
-            setReady(true);
-        }
-    }
+    private SignalStrengthController mSsc;
+    private PersistableBundle mBundle;
 
     @Before
     public void setUp() throws Exception {
-        logd("SignalStrengthControllerTest setUp.");
         super.setUp(TAG);
 
         when(mPhone.getSubId()).thenReturn(ACTIVE_SUB_ID);
+        mSsc = new SignalStrengthController(mPhone);
+        replaceInstance(Handler.class, "mLooper", mHandler, mSsc.getLooper());
+        replaceInstance(Phone.class, "mLooper", mPhone, mSsc.getLooper());
 
-        mTestHandlerThread = new TestHandlerThread(TAG);
-        mTestHandlerThread.start();
-        mHandler = mTestHandlerThread.getThreadHandler();
-        waitUntilReady();
-        waitForLastHandlerAction(mHandler);
+        mBundle = mContextFixture.getCarrierConfigBundle();
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY,
+                new int[] {
+                        -110, /* SIGNAL_STRENGTH_POOR */
+                        -90, /* SIGNAL_STRENGTH_MODERATE */
+                        -80, /* SIGNAL_STRENGTH_GOOD */
+                        -65,  /* SIGNAL_STRENGTH_GREAT */
+                });
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY,
+                new int[] {
+                        -31, /* SIGNAL_STRENGTH_POOR */
+                        -19, /* SIGNAL_STRENGTH_MODERATE */
+                        -7, /* SIGNAL_STRENGTH_GOOD */
+                        6  /* SIGNAL_STRENGTH_GREAT */
+                });
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY,
+                new int[] {
+                        -5, /* SIGNAL_STRENGTH_POOR */
+                        5, /* SIGNAL_STRENGTH_MODERATE */
+                        15, /* SIGNAL_STRENGTH_GOOD */
+                        30  /* SIGNAL_STRENGTH_GREAT */
+                });
+        processAllMessages();
     }
 
-
     @After
     public void tearDown() throws Exception {
-        mSignalStrengthController = null;
-        if (mTestHandlerThread != null) {
-            mTestHandlerThread.quit();
-            mTestHandlerThread.join();
-        }
-
+        mSsc = null;
         super.tearDown();
     }
 
@@ -109,9 +140,9 @@
                 true /* shouldReportSystemWhileIdle */
         );
 
-        mSignalStrengthController.setSignalStrengthUpdateRequest(INVALID_SUB_ID, CALLING_UID,
+        mSsc.setSignalStrengthUpdateRequest(INVALID_SUB_ID, CALLING_UID,
                 request, Message.obtain(mHandler));
-        waitForLastHandlerAction(mHandler);
+        processAllMessages();
 
         verify(mPhone).setAlwaysReportSignalStrength(eq(false));
     }
@@ -128,9 +159,9 @@
                 false /* shouldReportSystemWhileIdle */
         );
 
-        mSignalStrengthController.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+        mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
                 request, Message.obtain(mHandler));
-        waitForLastHandlerAction(mHandler);
+        processAllMessages();
 
         verify(mPhone).setAlwaysReportSignalStrength(eq(true));
     }
@@ -147,9 +178,9 @@
                 true /* shouldReportSystemWhileIdle */
         );
 
-        mSignalStrengthController.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+        mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
                 request, Message.obtain(mHandler));
-        waitForLastHandlerAction(mHandler);
+        processAllMessages();
 
         verify(mPhone).setAlwaysReportSignalStrength(eq(true));
     }
@@ -161,7 +192,7 @@
     public void shouldHonorSystemThresholds_deviceIsHighPowered_returnTrue() {
         when(mPhone.isDeviceIdle()).thenReturn(false);
 
-        assertThat(mSignalStrengthController.shouldHonorSystemThresholds()).isTrue();
+        assertThat(mSsc.shouldHonorSystemThresholds()).isTrue();
     }
 
     /**
@@ -172,7 +203,7 @@
     public void shouldHonorSystemThresholds_deviceIdle_noSignalRequest_returnTrue() {
         when(mPhone.isDeviceIdle()).thenReturn(true);
 
-        assertThat(mSignalStrengthController.shouldHonorSystemThresholds()).isFalse();
+        assertThat(mSsc.shouldHonorSystemThresholds()).isFalse();
     }
 
     /**
@@ -188,11 +219,11 @@
                 false /* shouldReportWhileIdle*/,
                 true /* shouldReportSystemWhileIdle */
         );
-        mSignalStrengthController.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+        mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
                 request, Message.obtain(mHandler));
-        waitForLastHandlerAction(mHandler);
+        processAllMessages();
 
-        assertThat(mSignalStrengthController.shouldHonorSystemThresholds()).isTrue();
+        assertThat(mSsc.shouldHonorSystemThresholds()).isTrue();
     }
 
     /**
@@ -201,7 +232,7 @@
      */
     @Test
     public void shouldEnableSignalThresholdForAppRequest_noRequest_returnFalse() {
-        assertThat(mSignalStrengthController.shouldEnableSignalThresholdForAppRequest(
+        assertThat(mSsc.shouldEnableSignalThresholdForAppRequest(
                 AccessNetworkConstants.AccessNetworkType.GERAN,
                 SIGNAL_MEASUREMENT_TYPE_RSSI,
                 ACTIVE_SUB_ID,
@@ -220,11 +251,11 @@
                 false /* shouldReportWhileIdle*/,
                 false /* shouldReportSystemWhileIdle */
         );
-        mSignalStrengthController.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+        mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
                 request, Message.obtain(mHandler));
-        waitForLastHandlerAction(mHandler);
+        processAllMessages();
 
-        assertThat(mSignalStrengthController.shouldEnableSignalThresholdForAppRequest(
+        assertThat(mSsc.shouldEnableSignalThresholdForAppRequest(
                 AccessNetworkConstants.AccessNetworkType.GERAN,
                 SIGNAL_MEASUREMENT_TYPE_RSSI,
                 ACTIVE_SUB_ID,
@@ -244,11 +275,11 @@
                 false /* shouldReportWhileIdle*/,
                 false /* shouldReportSystemWhileIdle */
         );
-        mSignalStrengthController.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+        mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
                 request, Message.obtain(mHandler));
-        waitForLastHandlerAction(mHandler);
+        processAllMessages();
 
-        assertThat(mSignalStrengthController.shouldEnableSignalThresholdForAppRequest(
+        assertThat(mSsc.shouldEnableSignalThresholdForAppRequest(
                 AccessNetworkConstants.AccessNetworkType.GERAN,
                 SIGNAL_MEASUREMENT_TYPE_RSSI,
                 ACTIVE_SUB_ID,
@@ -268,11 +299,11 @@
                 true /* shouldReportWhileIdle*/,
                 false /* shouldReportSystemWhileIdle */
         );
-        mSignalStrengthController.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+        mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
                 request, Message.obtain(mHandler));
-        waitForLastHandlerAction(mHandler);
+        processAllMessages();
 
-        assertThat(mSignalStrengthController.shouldEnableSignalThresholdForAppRequest(
+        assertThat(mSsc.shouldEnableSignalThresholdForAppRequest(
                 AccessNetworkConstants.AccessNetworkType.GERAN,
                 SIGNAL_MEASUREMENT_TYPE_RSSI,
                 ACTIVE_SUB_ID,
@@ -292,11 +323,11 @@
                 false /* shouldReportWhileIdle*/,
                 true /* shouldReportSystemWhileIdle */
         );
-        mSignalStrengthController.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+        mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
                 request, Message.obtain(mHandler));
-        waitForLastHandlerAction(mHandler);
+        processAllMessages();
 
-        assertThat(mSignalStrengthController.shouldEnableSignalThresholdForAppRequest(
+        assertThat(mSsc.shouldEnableSignalThresholdForAppRequest(
                 AccessNetworkConstants.AccessNetworkType.GERAN,
                 SIGNAL_MEASUREMENT_TYPE_RSSI,
                 ACTIVE_SUB_ID,
@@ -337,22 +368,262 @@
                     false /* shouldReportWhileIdle*/,
                     false /* shouldReportSystemWhileIdle */
             );
-            mSignalStrengthController.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+            mSsc.setSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
                     request, Message.obtain(mHandler));
-            waitForLastHandlerAction(mHandler);
+            processAllMessages();
 
-            assertThat(mSignalStrengthController.getConsolidatedSignalThresholds(
+            assertThat(mSsc.getConsolidatedSignalThresholds(
                     ran, measurement, systemThresholds, hysteresis
             )).isEqualTo(target);
 
             // Each pair in the Map is tested separately (instead of cumulatively).
             // Remove the request once it is done.
-            mSignalStrengthController.clearSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
+            mSsc.clearSignalStrengthUpdateRequest(ACTIVE_SUB_ID, CALLING_UID,
                     request, Message.obtain(mHandler));
-            waitForLastHandlerAction(mHandler);
+            processAllMessages();
         }
     }
 
+    @Test
+    @MediumTest
+    public void testSignalStrength() {
+        // Send in GSM Signal Strength Info and expect isGsm == true
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(-53, 0, SignalStrength.INVALID),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr());
+
+        sendSignalStrength(ss);
+        assertEquals(mSsc.getSignalStrength(), ss);
+        assertEquals(mSsc.getSignalStrength().isGsm(), true);
+
+        // Send in CDMA+LTE Signal Strength Info and expect isGsm == true
+        ss = new SignalStrength(
+                new CellSignalStrengthCdma(-90, -12,
+                        SignalStrength.INVALID, SignalStrength.INVALID, SignalStrength.INVALID),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(
+                        -110, -114, -5, 0, SignalStrength.INVALID, SignalStrength.INVALID),
+                new CellSignalStrengthNr());
+
+        sendSignalStrength(ss);
+        assertEquals(mSsc.getSignalStrength(), ss);
+        assertEquals(mSsc.getSignalStrength().isGsm(), true);
+
+        // Send in CDMA-only Signal Strength Info and expect isGsm == false
+        ss = new SignalStrength(
+                new CellSignalStrengthCdma(-90, -12,
+                        SignalStrength.INVALID, SignalStrength.INVALID, SignalStrength.INVALID),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr());
+
+        sendSignalStrength(ss);
+        assertEquals(mSsc.getSignalStrength(), ss);
+        assertEquals(mSsc.getSignalStrength().isGsm(), false);
+    }
+
+    @Test
+    public void testLteSignalStrengthReportingCriteria() {
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(
+                        -110, /* rssi */
+                        -114, /* rsrp */
+                        -5, /* rsrq */
+                        0, /* rssnr */
+                        SignalStrength.INVALID, /* cqi */
+                        SignalStrength.INVALID /* ta */),
+                new CellSignalStrengthNr());
+
+        mBundle.putBoolean(CarrierConfigManager.KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL,
+                true);
+
+        sendCarrierConfigUpdate();
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        // Default thresholds are POOR=-115 MODERATE=-105 GOOD=-95 GREAT=-85
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR, mSsc.getSignalStrength().getLevel());
+
+        int[] lteThresholds = {
+                -130, // SIGNAL_STRENGTH_POOR
+                -120, // SIGNAL_STRENGTH_MODERATE
+                -110, // SIGNAL_STRENGTH_GOOD
+                -100,  // SIGNAL_STRENGTH_GREAT
+        };
+        mBundle.putIntArray(CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+                lteThresholds);
+        sendCarrierConfigUpdate();
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(mSsc.getSignalStrength().getLevel(),
+                CellSignalStrength.SIGNAL_STRENGTH_MODERATE);
+    }
+
+    @Test
+    public void test5gNrSignalStrengthReportingCriteria_UseSsRsrp() {
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr(
+                        -139, /** csiRsrp NONE */
+                        -20, /** csiRsrq NONE */
+                        -23, /** CsiSinr NONE */
+                        -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
+                        -20, /** SsRsrq NONE */
+                        -23) /** SsSinr NONE */
+        );
+
+        // SSRSRP = 1 << 0
+        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
+                CellSignalStrengthNr.USE_SSRSRP);
+        sendCarrierConfigUpdate();
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_GREAT, mSsc.getSignalStrength().getLevel());
+    }
+
+    @Test
+    public void test5gNrSignalStrengthReportingCriteria_UseSsRsrpAndSsRsrq() {
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr(
+                        -139, /** csiRsrp NONE */
+                        -20, /** csiRsrq NONE */
+                        -23, /** CsiSinr NONE */
+                        -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
+                        -32, /** SsRsrq NONE */
+                        -23) /** SsSinr NONE */
+        );
+
+        // SSRSRP = 1 << 0 | SSSINR = 1 << 2
+        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
+                CellSignalStrengthNr.USE_SSRSRP | CellSignalStrengthNr.USE_SSRSRQ);
+        sendCarrierConfigUpdate();
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
+                mSsc.getSignalStrength().getLevel());
+    }
+
+    @Test
+    public void test5gNrSignalStrengthReportingCriteria_ConfiguredThresholds() {
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr(
+                        -139, /** csiRsrp NONE */
+                        -20, /** csiRsrq NONE */
+                        -23, /** CsiSinr NONE */
+                        -44, /** SsRsrp SIGNAL_STRENGTH_GREAT */
+                        -20, /** SsRsrq NONE */
+                        -23) /** SsSinr NONE */
+        );
+
+        // SSRSRP = 1 << 0
+        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
+                CellSignalStrengthNr.USE_SSRSRP);
+        sendCarrierConfigUpdate();
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_GREAT, mSsc.getSignalStrength().getLevel());
+
+        int[] nrSsRsrpThresholds = {
+                -45, // SIGNAL_STRENGTH_POOR
+                -40, // SIGNAL_STRENGTH_MODERATE
+                -37, // SIGNAL_STRENGTH_GOOD
+                -34,  // SIGNAL_STRENGTH_GREAT
+        };
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY,
+                nrSsRsrpThresholds);
+        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
+                CellSignalStrengthNr.USE_SSRSRP);
+        sendCarrierConfigUpdate();
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(CellSignalStrength.SIGNAL_STRENGTH_POOR,
+                mSsc.getSignalStrength().getLevel());
+    }
+
+    @Test
+    public void testWcdmaSignalStrengthReportingCriteria() {
+        SignalStrength ss = new SignalStrength(
+                new CellSignalStrengthCdma(),
+                new CellSignalStrengthGsm(),
+                new CellSignalStrengthWcdma(-79, 0, -85, -5),
+                new CellSignalStrengthTdscdma(),
+                new CellSignalStrengthLte(),
+                new CellSignalStrengthNr());
+
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(mSsc.getSignalStrength().getLevel(), CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+
+        int[] wcdmaThresholds = {
+                -110, // SIGNAL_STRENGTH_POOR
+                -100, // SIGNAL_STRENGTH_MODERATE
+                -90, // SIGNAL_STRENGTH_GOOD
+                -80  // SIGNAL_STRENGTH_GREAT
+        };
+        mBundle.putIntArray(CarrierConfigManager.KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY,
+                wcdmaThresholds);
+        mBundle.putString(
+                CarrierConfigManager.KEY_WCDMA_DEFAULT_SIGNAL_STRENGTH_MEASUREMENT_STRING,
+                "rscp");
+        sendCarrierConfigUpdate();
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+        assertEquals(mSsc.getSignalStrength().getLevel(), CellSignalStrength.SIGNAL_STRENGTH_GOOD);
+    }
+
+    private void sendCarrierConfigUpdate() {
+        CarrierConfigManager mockConfigManager = Mockito.mock(CarrierConfigManager.class);
+        when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
+                .thenReturn(mockConfigManager);
+        when(mockConfigManager.getConfigForSubId(anyInt())).thenReturn(mBundle);
+
+        Intent intent = new Intent().setAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, PHONE_ID);
+        mContext.sendBroadcast(intent);
+        processAllMessages();
+    }
+
+    private void sendSignalStrength(SignalStrength ss) {
+        mSimulatedCommands.setSignalStrength(ss);
+        mSimulatedCommands.notifySignalStrength();
+        processAllMessages();
+    }
+
     private SignalThresholdInfo createTestSignalThresholdInfo() {
         SignalThresholdInfo info = new SignalThresholdInfo.Builder()
                 .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
index b35cdd1..8dd6a81 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
@@ -1189,6 +1189,13 @@
         }
     }
 
+    public void triggerNITZupdate(String NITZStr, long ageMs) {
+        if (NITZStr != null) {
+            mNITZTimeRegistrant.notifyRegistrant(new AsyncResult (null, new Object[]{NITZStr,
+                    SystemClock.elapsedRealtime(), ageMs}, null));
+        }
+    }
+
     @Override
     public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
             boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId,
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
index 1d5bce6..c04bcfb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
@@ -724,4 +724,67 @@
         processAllMessages();
         assertEquals(lceList, mLinkCapacityEstimateList);
     }
+
+    @Test
+    public void testPreciseDataConnectionStateChangedForInvalidSubId() {
+        //set dual sim
+        doReturn(2).when(mTelephonyManager).getActiveModemCount();
+        mContext.sendBroadcast(new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED));
+        // set default slot
+        mContext.sendBroadcast(new Intent(ACTION_DEFAULT_SUBSCRIPTION_CHANGED)
+                .putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, 2)
+                .putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0));
+        processAllMessages();
+
+        final int subId = 1;
+        int[] events = {TelephonyCallback.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED};
+        doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
+        doReturn(1).when(mMockSubInfo).getSimSlotIndex();
+
+        mTelephonyRegistry.listenWithEventList(subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, true);
+        processAllMessages();
+
+        // notify data connection with invalid sub id and default phone id
+        mTelephonyRegistry.notifyDataConnectionForSubscriber(
+                /*default phoneId*/ 0, /*invalid subId*/ -1,
+                new PreciseDataConnectionState.Builder()
+                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                        .setId(1)
+                        .setState(TelephonyManager.DATA_CONNECTED)
+                        .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE)
+                        .setApnSetting(new ApnSetting.Builder()
+                                .setApnTypeBitmask(ApnSetting.TYPE_IMS)
+                                .setApnName("ims")
+                                .setEntryName("ims")
+                                .build())
+                        .setLinkProperties(new LinkProperties())
+                        .setFailCause(0)
+                        .build());
+
+        processAllMessages();
+
+        assertEquals(0, mTelephonyCallback.invocationCount.get());
+
+        // notify data connection with invalid sub id and default phone id
+        mTelephonyRegistry.notifyDataConnectionForSubscriber(
+                /*target phoneId*/ 1, /*invalid subId*/ -1,
+                new PreciseDataConnectionState.Builder()
+                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                        .setId(2)
+                        .setState(TelephonyManager.DATA_SUSPENDED)
+                        .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE)
+                        .setApnSetting(new ApnSetting.Builder()
+                                .setApnTypeBitmask(ApnSetting.TYPE_IMS)
+                                .setApnName("ims")
+                                .setEntryName("ims")
+                                .build())
+                        .setLinkProperties(new LinkProperties())
+                        .setFailCause(0)
+                        .build());
+
+        processAllMessages();
+
+        assertEquals(1, mTelephonyCallback.invocationCount.get());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/LinkBandwidthEstimatorTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/LinkBandwidthEstimatorTest.java
index 80fe817..3467f3f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/LinkBandwidthEstimatorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/LinkBandwidthEstimatorTest.java
@@ -105,6 +105,7 @@
         when(mTelephonyFacade.getMobileTxBytes()).thenReturn(0L);
         when(mTelephonyFacade.getMobileTxBytes()).thenReturn(0L);
         when(mPhone.getCurrentCellIdentity()).thenReturn(mCellIdentity);
+        // Note that signal level is 0 before 1st MSG_SIGNAL_STRENGTH_CHANGED
         when(mPhone.getSubId()).thenReturn(1);
         when(mSignalStrength.getDbm()).thenReturn(-100);
         when(mSignalStrength.getLevel()).thenReturn(1);
@@ -330,17 +331,43 @@
 
         verifyUpdateBandwidth(-1, 19_597);
 
-        addTxBytes(20_000L);
-        addRxBytes(50_000L);
+        when(mSignalStrength.getLevel()).thenReturn(1);
         when(mSignalStrength.getDbm()).thenReturn(-110);
         mLBE.obtainMessage(MSG_SIGNAL_STRENGTH_CHANGED, mSignalStrength).sendToTarget();
         addElapsedTime(6000);
         moveTimeForward(6000);
         processAllMessages();
+        verifyUpdateBandwidth(-1, 19_535);
 
+        when(mSignalStrength.getLevel()).thenReturn(2);
+        when(mSignalStrength.getDbm()).thenReturn(-90);
+        mLBE.obtainMessage(MSG_SIGNAL_STRENGTH_CHANGED, mSignalStrength).sendToTarget();
+        addElapsedTime(6000);
+        moveTimeForward(6000);
+        processAllMessages();
         verifyUpdateBandwidth(-1, -1);
+
+        for (int i = 0; i < BW_STATS_COUNT_THRESHOLD + 2; i++) {
+            addTxBytes(10_000L);
+            addRxBytes(1000_000L);
+            addElapsedTime(5_100);
+            moveTimeForward(5_100);
+            processAllMessages();
+            mLBE.obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, new ModemActivityInfo(
+                    i * 5_100L, 0, 0, TX_TIME_2_MS, i * RX_TIME_2_MS)).sendToTarget();
+            processAllMessages();
+        }
+
+        when(mSignalStrength.getLevel()).thenReturn(1);
+        when(mSignalStrength.getDbm()).thenReturn(-110);
+        mLBE.obtainMessage(MSG_SIGNAL_STRENGTH_CHANGED, mSignalStrength).sendToTarget();
+        addElapsedTime(6000);
+        moveTimeForward(6000);
+        processAllMessages();
+        verifyUpdateBandwidth(-1, 30_821);
     }
 
+
     @Test
     public void testAvgBwForAllPossibleRat() throws Exception {
         Pair<Integer, Integer> values = mLBE.getStaticAvgBw(TelephonyManager.NETWORK_TYPE_GPRS);
@@ -401,7 +428,6 @@
         addRxBytes(19_000L);
         when(mServiceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED);
         when(mServiceState.getNrFrequencyRange()).thenReturn(ServiceState.FREQUENCY_RANGE_MMWAVE);
-        when(mSignalStrength.getLevel()).thenReturn(2);
         mLBE.obtainMessage(MSG_NR_FREQUENCY_CHANGED).sendToTarget();
         addElapsedTime(6000);
         moveTimeForward(6000);
@@ -634,7 +660,7 @@
 
 
     @Test
-    public void testVeryHighByteCountReturnNonNegativeValue() throws Exception {
+    public void testVeryHighRxLinkBandwidthEstimationIgnored() throws Exception {
         mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, true).sendToTarget();
         processAllMessages();
         mLBE.obtainMessage(MSG_SIGNAL_STRENGTH_CHANGED, mSignalStrength).sendToTarget();
@@ -650,11 +676,12 @@
             processAllMessages();
         }
 
+        // This will result in link bandwidth estimation value 128Gbps which is too high for LTE.
+        // So it will be ignored by the estimator.
         LinkBandwidthEstimator.NetworkBandwidth network = mLBE.lookupNetwork("310260", 366, "LTE");
 
-        assertEquals(BW_STATS_COUNT_THRESHOLD + 4, network.getCount(LINK_RX, 1));
+        assertEquals(0, network.getCount(LINK_RX, 1));
         assertEquals(0, network.getValue(LINK_TX, 1));
-        assertEquals(16_000_000_000L * 8 / 1024 * (BW_STATS_COUNT_THRESHOLD + 4),
-                network.getValue(LINK_RX, 1));
+        assertEquals(0, network.getValue(LINK_RX, 1));
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
index 5e98e50..84f2672 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
@@ -72,6 +72,7 @@
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -433,15 +434,14 @@
     @SmallTest
     public void testSetRedirectingAddress() {
         mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
-        ArrayList<String> forwardedNumber = new ArrayList<String>();
-        forwardedNumber.add("11111");
-        forwardedNumber.add("22222");
-        forwardedNumber.add("33333");
+        String[] forwardedNumber = new String[]{"11111", "22222", "33333"};
+        ArrayList<String> forwardedNumberList =
+                new ArrayList<String>(Arrays.asList(forwardedNumber));
 
         assertEquals(mConnectionUT.getForwardedNumber(), null);
-        mBundle.putStringArrayList(ImsCallProfile.EXTRA_FORWARDED_NUMBER, forwardedNumber);
+        mBundle.putStringArray(ImsCallProfile.EXTRA_FORWARDED_NUMBER, forwardedNumber);
         assertTrue(mConnectionUT.update(mImsCall, Call.State.ACTIVE));
-        assertEquals(forwardedNumber, mConnectionUT.getForwardedNumber());
+        assertEquals(forwardedNumberList, mConnectionUT.getForwardedNumber());
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/CallQualityMetricsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/CallQualityMetricsTest.java
index e530425..a0341b7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/CallQualityMetricsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/CallQualityMetricsTest.java
@@ -28,15 +28,20 @@
 import android.telephony.CellSignalStrengthWcdma;
 import android.telephony.SignalStrength;
 
+import com.android.internal.telephony.SignalStrengthController;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.CallQualitySummary;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
 
 public class CallQualityMetricsTest extends TelephonyTest {
 
+    @Mock
+    SignalStrengthController mSsc;
+
     private CallQualityMetrics mCallQualityMetrics;
 
     @Before
@@ -48,6 +53,7 @@
         // default phone from the ImsPhone and uses that to get the ServiceStateTracker, therefore
         // we need to mock the default phone as well.
         when(mPhone.getDefaultPhone()).thenReturn(mPhone);
+        when(mPhone.getSignalStrengthController()).thenReturn(mSsc);
     }
 
     @After
@@ -264,7 +270,7 @@
                 new CellSignalStrengthTdscdma(),
                 lteSs1,
                 new CellSignalStrengthNr());
-        when(mSST.getSignalStrength()).thenReturn(ss1);
+        when(mSsc.getSignalStrength()).thenReturn(ss1);
         mCallQualityMetrics.saveCallQuality(cq1);
 
         // save good quality with low rssnr
@@ -280,7 +286,7 @@
                 new CellSignalStrengthTdscdma(),
                 lteSs2,
                 new CellSignalStrengthNr());
-        when(mSST.getSignalStrength()).thenReturn(ss2);
+        when(mSsc.getSignalStrength()).thenReturn(ss2);
         mCallQualityMetrics.saveCallQuality(cq1);
 
         CallQualitySummary dlSummary = mCallQualityMetrics.getCallQualitySummaryDl();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
index 9651741..988e252 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
@@ -1311,9 +1311,9 @@
         expectedCall.ratSwitchCount = 1L;
         expectedCall.setupFailed = true;
         expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
-        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.codecBitmask = 0L;
         expectedCall.mainCodecQuality =
-                VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
+                VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN;
         VoiceCallRatUsage expectedRatUsageLte =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 3000L, 1L);
@@ -1331,8 +1331,6 @@
         mVoiceCallSessionStats0.setTimeMillis(3000L);
         setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_UMTS);
         mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
-        mVoiceCallSessionStats0.setTimeMillis(3100L);
-        mVoiceCallSessionStats0.onAudioCodecChanged(mGsmConnection0, DriverCall.AUDIO_QUALITY_AMR);
         mVoiceCallSessionStats0.setTimeMillis(15000L);
         doReturn(DisconnectCause.LOST_SIGNAL).when(mGsmConnection0).getDisconnectCause();
         mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
@@ -1435,7 +1433,8 @@
         expectedCall.setupFailed = true;
         expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
         expectedCall.bandAtEnd = 0;
-        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
+        expectedCall.codecBitmask =
+                (1L << AudioCodec.AUDIO_CODEC_AMR) | (1L << AudioCodec.AUDIO_CODEC_AMR_WB);
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         VoiceCallRatUsage expectedRatUsage =
@@ -1448,10 +1447,12 @@
         mVoiceCallSessionStats0.setTimeMillis(2500L);
         doReturn(Call.State.INCOMING).when(mCsCall0).getState();
         doReturn(Call.State.INCOMING).when(mGsmConnection0).getState();
+        doReturn(DriverCall.AUDIO_QUALITY_AMR_WB).when(mGsmConnection0).getAudioCodec();
         doReturn(DisconnectCause.NOT_DISCONNECTED).when(mGsmConnection0).getDisconnectCause();
         mVoiceCallSessionStats0.onRilCallListChanged(List.of(mGsmConnection0));
         mVoiceCallSessionStats0.setTimeMillis(3000L);
         mVoiceCallSessionStats0.onAudioCodecChanged(mGsmConnection0, DriverCall.AUDIO_QUALITY_AMR);
+        doReturn(DriverCall.AUDIO_QUALITY_AMR).when(mGsmConnection0).getAudioCodec();
         mVoiceCallSessionStats0.setTimeMillis(15000L);
         doReturn(DisconnectCause.NORMAL).when(mGsmConnection0).getDisconnectCause();
         doReturn(PreciseDisconnectCause.CALL_REJECTED)
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactoryTest.java
index 2339f08..1997114 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactoryTest.java
@@ -19,16 +19,17 @@
 import static com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.createBogusElapsedRealtimeCheck;
 import static com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.createIgnoreNitzPropertyCheck;
 import static com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.createRateLimitCheck;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_AGE;
 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO1;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.os.TimestampedValue;
-
 import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzSignal;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.NitzSignalInputFilterPredicateImpl;
 import com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.TrivalentPredicate;
@@ -57,8 +58,8 @@
     @Test
     public void testNitzSignalInputFilterPredicateImpl_nullSecondArgumentRejected() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         TrivalentPredicate[] triPredicates = {};
         NitzSignalInputFilterPredicateImpl impl =
                 new NitzSignalInputFilterPredicateImpl(triPredicates);
@@ -72,8 +73,8 @@
     @Test
     public void testNitzSignalInputFilterPredicateImpl_defaultIsTrue() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal = scenario
-                .createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         NitzSignalInputFilterPredicateImpl impl =
                 new NitzSignalInputFilterPredicateImpl(new TrivalentPredicate[0]);
         assertTrue(impl.mustProcessNitzSignal(null, nitzSignal));
@@ -82,8 +83,8 @@
     @Test
     public void testNitzSignalInputFilterPredicateImpl_nullIsIgnored() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         TrivalentPredicate nullPredicate = (x, y) -> null;
         TrivalentPredicate[] triPredicates = { nullPredicate };
         NitzSignalInputFilterPredicateImpl impl =
@@ -94,8 +95,8 @@
     @Test
     public void testNitzSignalInputFilterPredicateImpl_trueIsHonored() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         TrivalentPredicate nullPredicate = (x, y) -> null;
         TrivalentPredicate truePredicate = (x, y) -> true;
         TrivalentPredicate exceptionPredicate = (x, y) -> {
@@ -114,8 +115,8 @@
     @Test
     public void testNitzSignalInputFilterPredicateImpl_falseIsHonored() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         TrivalentPredicate nullPredicate = (x, y) -> null;
         TrivalentPredicate falsePredicate = (x, y) -> false;
         TrivalentPredicate exceptionPredicate = (x, y) -> {
@@ -145,24 +146,42 @@
     @Test
     public void testTrivalentPredicate_bogusElapsedRealtimeCheck() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        long elapsedRealtimeClock = mFakeDeviceState.elapsedRealtime();
-        TimestampedValue<NitzData> nitzSignal = scenario.createNitzSignal(elapsedRealtimeClock);
+        long elapsedRealtimeMillis = mFakeDeviceState.elapsedRealtimeMillis();
+        NitzSignal baseNitzSignal =
+                scenario.createNitzSignal(elapsedRealtimeMillis, ARBITRARY_AGE);
 
         TrivalentPredicate triPredicate =
                 createBogusElapsedRealtimeCheck(mContext, mFakeDeviceState);
-        assertNull(triPredicate.mustProcessNitzSignal(null, nitzSignal));
+        assertNull(triPredicate.mustProcessNitzSignal(null, baseNitzSignal));
 
         // Any signal that claims to be from the future must be rejected.
-        TimestampedValue<NitzData> bogusNitzSignal = new TimestampedValue<>(
-                elapsedRealtimeClock + 1, nitzSignal.getValue());
-        assertFalse(triPredicate.mustProcessNitzSignal(null, bogusNitzSignal));
+        {
+            long receiptElapsedMillis = elapsedRealtimeMillis + 1;
+            long ageMillis = 0;
+            NitzSignal bogusNitzSignal = new NitzSignal(
+                    receiptElapsedMillis, baseNitzSignal.getNitzData(), ageMillis);
+            assertFalse(triPredicate.mustProcessNitzSignal(null, bogusNitzSignal));
+        }
+
+        // Age should be ignored: the predicate is intended to check receipt time isn't obviously
+        // corrupt / fabricated to be in the future. Larger ages could imply that the NITZ was
+        // received by the modem before the elapsed realtime clock started ticking, but we don't
+        // currently check for that.
+        {
+            long receiptElapsedMillis = elapsedRealtimeMillis + 1;
+            long ageMillis = 10000;
+            NitzSignal bogusNitzSignal = new NitzSignal(
+                    receiptElapsedMillis, baseNitzSignal.getNitzData(), ageMillis);
+
+            assertFalse(triPredicate.mustProcessNitzSignal(null, bogusNitzSignal));
+        }
     }
 
     @Test
     public void testTrivalentPredicate_noOldSignalCheck() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
 
         TrivalentPredicate triPredicate =
                 NitzSignalInputFilterPredicateFactory.createNoOldSignalCheck();
@@ -171,36 +190,108 @@
     }
 
     @Test
-    public void testTrivalentPredicate_rateLimitCheck_elapsedRealtime() {
+    public void testTrivalentPredicate_rateLimitCheck_elapsedRealtime_zeroAge() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
         int nitzSpacingThreshold = mFakeDeviceState.getNitzUpdateSpacingMillis();
-        NitzData baseNitzData = scenario.createNitzData();
+        // Change the other setting that can affect the predicate behavior so it is not a factor in
+        // the test.
+        mFakeDeviceState.setNitzUpdateDiffMillis(Integer.MAX_VALUE);
 
         TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
 
-        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtime();
-        TimestampedValue<NitzData> baseSignal =
-                new TimestampedValue<>(baseElapsedRealtimeMillis, baseNitzData);
+        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtimeMillis();
+        NitzData baseNitzData = scenario.createNitzData();
+        int baseAgeMillis = 0;
+        NitzSignal baseNitzSignal =
+                new NitzSignal(baseElapsedRealtimeMillis, baseNitzData, baseAgeMillis);
 
         // Two identical signals: no spacing so the new signal should not be processed.
-        {
-            assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, baseSignal));
-        }
+        assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, baseNitzSignal));
 
-        // Two signals not spaced apart enough: the new signal should not processed.
+        // Two signals not spaced apart enough in receipt time: the new signal should not be
+        // processed.
         {
-            int elapsedTimeIncrement = nitzSpacingThreshold - 1;
-            TimestampedValue<NitzData> newSignal =
-                    createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
-            assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, newSignal));
+            int timeAdjustment = nitzSpacingThreshold - 1;
+            int utcAdjustment = 0;
+            long ageAdjustment = 0;
+            NitzSignal newSignal = createAdjustedNitzSignal(
+                    baseNitzSignal, timeAdjustment, utcAdjustment, ageAdjustment);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, newSignal));
         }
 
         // Two signals spaced apart: the new signal should be processed.
         {
-            int elapsedTimeIncrement = nitzSpacingThreshold + 1;
-            TimestampedValue<NitzData> newSignal =
-                    createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
-            assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, newSignal));
+            int timeAdjustment = nitzSpacingThreshold + 1;
+            int utcAdjustment = 0;
+            long ageAdjustment = 0;
+            NitzSignal newSignal = createAdjustedNitzSignal(
+                    baseNitzSignal, timeAdjustment, utcAdjustment, ageAdjustment);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, newSignal));
+        }
+    }
+
+    @Test
+    public void testTrivalentPredicate_rateLimitCheck_elapsedRealtime_withAge() {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        int nitzSpacingThreshold = 60000;
+        mFakeDeviceState.setNitzUpdateSpacingMillis(nitzSpacingThreshold);
+
+        // Change the other setting that can affect the predicate behavior so it is not a factor in
+        // the test.
+        mFakeDeviceState.setNitzUpdateDiffMillis(Integer.MAX_VALUE);
+
+        TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
+
+        // Create a NITZ signal to be the first of two NITZ signals received.
+        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtimeMillis();
+        NitzData baseNitzData = scenario.createNitzData();
+        int baseAgeMillis = 20000;
+        NitzSignal baseNitzSignal =
+                new NitzSignal(baseElapsedRealtimeMillis, baseNitzData, baseAgeMillis);
+
+        // Two identical signals: no spacing so the new signal should not be processed.
+        assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, baseNitzSignal));
+
+        // Two signals not spaced apart enough: the new signal should not be processed.
+        // The age is changed to prove it doesn't affect this check.
+        {
+            int elapsedRealtimeAdjustment = nitzSpacingThreshold - 1;
+            int utcAdjustment = 0;
+            long ageAdjustment = 10000;
+            NitzSignal newSignal = createAdjustedNitzSignal(
+                    baseNitzSignal, elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, newSignal));
+        }
+
+        // Two signals not spaced apart enough: the new signal should not be processed.
+        // The age is changed to prove it doesn't affect this check.
+        {
+            int elapsedRealtimeAdjustment = nitzSpacingThreshold - 1;
+            int utcAdjustment = 0;
+            long ageAdjustment = -10000;
+            NitzSignal newSignal = createAdjustedNitzSignal(
+                    baseNitzSignal, elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, newSignal));
+        }
+
+        // Two signals spaced far enough apart: the new signal should be processed.
+        {
+            int elapsedRealtimeAdjustment = nitzSpacingThreshold + 1;
+            int utcAdjustment = 0;
+            long ageAdjustment = 10000;
+            NitzSignal newSignal = createAdjustedNitzSignal(
+                    baseNitzSignal, elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, newSignal));
+        }
+
+        // Two signals spaced far enough apart: the new signal should be processed.
+        {
+            int elapsedRealtimeAdjustment = nitzSpacingThreshold + 1;
+            int utcAdjustment = 0;
+            long ageAdjustment = -10000;
+            NitzSignal newSignal = createAdjustedNitzSignal(
+                    baseNitzSignal, elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, newSignal));
         }
     }
 
@@ -208,20 +299,23 @@
     public void testTrivalentPredicate_rateLimitCheck_offsetDifference() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
         int nitzSpacingThreshold = mFakeDeviceState.getNitzUpdateSpacingMillis();
-        NitzData baseNitzData = scenario.createNitzData();
 
         TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
 
-        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtime();
-        TimestampedValue<NitzData> baseSignal =
-                new TimestampedValue<>(baseElapsedRealtimeMillis, baseNitzData);
+        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtimeMillis();
+        NitzData baseNitzData = scenario.createNitzData();
+        long baseAgeMillis = 0;
+        NitzSignal baseNitzSignal = new NitzSignal(
+                baseElapsedRealtimeMillis, baseNitzData, baseAgeMillis);
 
-        // Create a new NitzSignal that should be filtered.
-        int elapsedTimeIncrement = nitzSpacingThreshold - 1;
-        TimestampedValue<NitzData> intermediateNitzSignal =
-                createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
-        NitzData intermediateNitzData = intermediateNitzSignal.getValue();
-        assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, intermediateNitzSignal));
+        // Create a new NitzSignal that would normally be filtered.
+        int timeAdjustment = nitzSpacingThreshold - 1;
+        long ageAdjustment = 0;
+        NitzSignal intermediateNitzSignal = createAdjustedNitzSignal(
+                baseNitzSignal, timeAdjustment, timeAdjustment, ageAdjustment);
+        NitzData intermediateNitzData = intermediateNitzSignal.getNitzData();
+        assertAgeAdjustedUtcTimeIsIdentical(baseNitzSignal, intermediateNitzSignal);
+        assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, intermediateNitzSignal));
 
         // Two signals spaced apart so that the second would be filtered, but they contain different
         // offset information so should be detected as "different" and processed.
@@ -232,102 +326,181 @@
                     intermediateNitzData.getDstAdjustmentMillis(),
                     intermediateNitzData.getCurrentTimeInMillis(),
                     intermediateNitzData.getEmulatorHostTimeZone());
-            TimestampedValue<NitzData> differentOffsetSignal = new TimestampedValue<>(
-                    baseSignal.getReferenceTimeMillis() + elapsedTimeIncrement,
-                    differentOffsetNitzData);
-            assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, differentOffsetSignal));
+            NitzSignal differentOffsetSignal = new NitzSignal(
+                    baseNitzSignal.getReceiptElapsedRealtimeMillis() + timeAdjustment,
+                    differentOffsetNitzData,
+                    baseNitzSignal.getAgeMillis());
+            assertAgeAdjustedUtcTimeIsIdentical(baseNitzSignal, differentOffsetSignal);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, differentOffsetSignal));
         }
     }
 
     @Test
-    public void testTrivalentPredicate_rateLimitCheck_utcTimeDifferences() {
+    public void testTrivalentPredicate_rateLimitCheck_utcTimeDifferences_withZeroAge() {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        int nitzSpacingThreshold = mFakeDeviceState.getNitzUpdateSpacingMillis();
+        // Change the other setting that can affect the predicate behavior so it is not a factor in
+        // the test.
+        mFakeDeviceState.setNitzUpdateSpacingMillis(Integer.MAX_VALUE);
         int nitzUtcDiffThreshold = mFakeDeviceState.getNitzUpdateDiffMillis();
-        NitzData baseNitzData = scenario.createNitzData();
 
         TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
 
-        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtime();
-        TimestampedValue<NitzData> baseSignal =
-                new TimestampedValue<>(baseElapsedRealtimeMillis, baseNitzData);
+        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtimeMillis();
+        NitzData baseNitzData = scenario.createNitzData();
+        int baseAgeMillis = 0;
+        NitzSignal baseNitzSignal =
+                new NitzSignal(baseElapsedRealtimeMillis, baseNitzData, baseAgeMillis);
 
-        // Create a new NitzSignal that should be filtered.
-        int elapsedTimeIncrement = nitzSpacingThreshold - 1;
-        TimestampedValue<NitzData> intermediateSignal =
-                createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
-        NitzData intermediateNitzData = intermediateSignal.getValue();
-        assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, intermediateSignal));
-
-        // Two signals spaced apart so that the second would normally be filtered and it contains
-        // a UTC time that is not sufficiently different.
+        // Two signals spaced contain UTC times that are not sufficiently different and so should be
+        // filtered.
         {
-            NitzData incrementedUtcTimeNitzData = NitzData.createForTests(
-                    intermediateNitzData.getLocalOffsetMillis(),
-                    intermediateNitzData.getDstAdjustmentMillis(),
-                    intermediateNitzData.getCurrentTimeInMillis() + nitzUtcDiffThreshold - 1,
-                    intermediateNitzData.getEmulatorHostTimeZone());
-
-            TimestampedValue<NitzData> incrementedNitzSignal = new TimestampedValue<>(
-                    intermediateSignal.getReferenceTimeMillis(), incrementedUtcTimeNitzData);
-            assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, incrementedNitzSignal));
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = nitzUtcDiffThreshold - 1;
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(baseNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
         }
 
-        // Two signals spaced apart so that the second would normally be filtered but it contains
-        // a UTC time that is sufficiently different.
+        // Two signals spaced contain UTC times that are not sufficiently different and so should be
+        // filtered.
         {
-            NitzData incrementedUtcTimeNitzData = NitzData.createForTests(
-                    intermediateNitzData.getLocalOffsetMillis(),
-                    intermediateNitzData.getDstAdjustmentMillis(),
-                    intermediateNitzData.getCurrentTimeInMillis() + nitzUtcDiffThreshold + 1,
-                    intermediateNitzData.getEmulatorHostTimeZone());
-
-            TimestampedValue<NitzData> incrementedNitzSignal = new TimestampedValue<>(
-                    intermediateSignal.getReferenceTimeMillis(), incrementedUtcTimeNitzData);
-            assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, incrementedNitzSignal));
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = -(nitzUtcDiffThreshold - 1);
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(baseNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
         }
 
-        // Two signals spaced apart so that the second would normally be filtered and it contains
-        // a UTC time that is not sufficiently different.
+        // Two signals spaced contain UTC times that are sufficiently different and so should not be
+        // filtered.
         {
-            NitzData decrementedUtcTimeNitzData = NitzData.createForTests(
-                    intermediateNitzData.getLocalOffsetMillis(),
-                    intermediateNitzData.getDstAdjustmentMillis(),
-                    intermediateNitzData.getCurrentTimeInMillis() - nitzUtcDiffThreshold + 1,
-                    intermediateNitzData.getEmulatorHostTimeZone());
-
-            TimestampedValue<NitzData> decrementedNitzSignal = new TimestampedValue<>(
-                    intermediateSignal.getReferenceTimeMillis(), decrementedUtcTimeNitzData);
-            assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, decrementedNitzSignal));
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = nitzUtcDiffThreshold + 1;
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(baseNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
         }
 
-        // Two signals spaced apart so that the second would normally be filtered but it contains
-        // a UTC time that is sufficiently different.
+        // Two signals spaced contain UTC times that are sufficiently different and so should not be
+        // filtered.
         {
-            NitzData decrementedUtcTimeNitzData = NitzData.createForTests(
-                    intermediateNitzData.getLocalOffsetMillis(),
-                    intermediateNitzData.getDstAdjustmentMillis(),
-                    intermediateNitzData.getCurrentTimeInMillis() + nitzUtcDiffThreshold + 1,
-                    intermediateNitzData.getEmulatorHostTimeZone());
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = -(nitzUtcDiffThreshold + 1);
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(baseNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
+        }
+    }
 
-            TimestampedValue<NitzData> decrementedNitzSignal = new TimestampedValue<>(
-                    intermediateSignal.getReferenceTimeMillis(), decrementedUtcTimeNitzData);
-            assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, decrementedNitzSignal));
+    @Test
+    public void testTrivalentPredicate_rateLimitCheck_utcTimeDifferences_withAge() {
+        Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+        // Change the other setting that can affect the predicate behavior so it is not a factor in
+        // the test.
+        mFakeDeviceState.setNitzUpdateSpacingMillis(Integer.MAX_VALUE);
+        int nitzUtcDiffThreshold = mFakeDeviceState.getNitzUpdateDiffMillis();
+
+        TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
+
+        long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtimeMillis();
+        NitzData baseNitzData = scenario.createNitzData();
+        int baseAgeMillis = 20000;
+        NitzSignal baseNitzSignal =
+                new NitzSignal(baseElapsedRealtimeMillis, baseNitzData, baseAgeMillis);
+
+        // This is another NitzSignal that represents the same time as baseNitzSignal, but it has
+        // been cached by the modem for a different amount of time, so has different values even
+        // though it encodes for the same UTC time. Used to construct test signals below.
+        int intermediateSignalAgeAdjustment = -10000;
+        int intermediateUtcAdjustment = 0;
+        NitzSignal intermediateNitzSignal = createAdjustedNitzSignal(baseNitzSignal,
+                intermediateSignalAgeAdjustment, intermediateUtcAdjustment,
+                intermediateSignalAgeAdjustment);
+        assertAgeAdjustedUtcTimeIsIdentical(baseNitzSignal, intermediateNitzSignal);
+
+        // Two signals spaced contain UTC times that are not sufficiently different and so should be
+        // filtered.
+        {
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = nitzUtcDiffThreshold - 1;
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(intermediateNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
+        }
+
+        // Two signals spaced contain UTC times that are not sufficiently different and so should be
+        // filtered.
+        {
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = -(nitzUtcDiffThreshold - 1);
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(intermediateNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertFalse(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
+        }
+
+        // Two signals spaced contain UTC times that are sufficiently different and so should not be
+        // filtered.
+        {
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = nitzUtcDiffThreshold + 1;
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(intermediateNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
+        }
+
+        // Two signals spaced contain UTC times that are sufficiently different and so should not be
+        // filtered.
+        {
+            int elapsedRealtimeAdjustment = 0;
+            int utcAdjustment = -(nitzUtcDiffThreshold + 1);
+            long ageAdjustment = 0;
+            NitzSignal nitzSignal = createAdjustedNitzSignal(intermediateNitzSignal,
+                    elapsedRealtimeAdjustment, utcAdjustment, ageAdjustment);
+            assertTrue(triPredicate.mustProcessNitzSignal(baseNitzSignal, nitzSignal));
         }
     }
 
     /**
-     * Creates an NITZ signal based on the the supplied signal but with all the fields related to
-     * elapsed time incremented by the specified number of milliseconds.
+     * Creates an NITZ signal based on the supplied signal but with all the fields associated with
+     * the time (receipt time, UTC and age) adjusted by the specified amounts.
      */
-    private static TimestampedValue<NitzData> createIncrementedNitzSignal(
-            TimestampedValue<NitzData> baseSignal, int incrementMillis) {
-        NitzData baseData = baseSignal.getValue();
-        return new TimestampedValue<>(baseSignal.getReferenceTimeMillis() + incrementMillis,
-                NitzData.createForTests(
-                        baseData.getLocalOffsetMillis(),
-                        baseData.getDstAdjustmentMillis(),
-                        baseData.getCurrentTimeInMillis() + incrementMillis,
-                        baseData.getEmulatorHostTimeZone()));
+    private static NitzSignal createAdjustedNitzSignal(
+            NitzSignal baseNitzSignal, int elapsedRealtimeMillisAdjustment, int utcMillisAdjustment,
+            long ageMillisAdjustment) {
+        long adjustedReceiptElapsedMillis =
+                baseNitzSignal.getReceiptElapsedRealtimeMillis() + elapsedRealtimeMillisAdjustment;
+        NitzData adjustedNitzData =
+                createAdjustedNitzData(baseNitzSignal.getNitzData(), utcMillisAdjustment);
+        long adjustedAgeMillis = baseNitzSignal.getAgeMillis() + ageMillisAdjustment;
+        return new NitzSignal(adjustedReceiptElapsedMillis, adjustedNitzData, adjustedAgeMillis);
+    }
+
+    /** Creates a new NitzData by adjusting the UTC time in the supplied NitzData */
+    private static NitzData createAdjustedNitzData(NitzData baseData, int utcMillisAdjustment) {
+        return NitzData.createForTests(
+                baseData.getLocalOffsetMillis(),
+                baseData.getDstAdjustmentMillis(),
+                baseData.getCurrentTimeInMillis() + utcMillisAdjustment,
+                baseData.getEmulatorHostTimeZone());
+    }
+
+    /**
+     * Used during tests to confirm that two NitzSignal test objects represent the same UTC time,
+     * even though their receipt times and ages may differ.
+     */
+    private static void assertAgeAdjustedUtcTimeIsIdentical(
+            NitzSignal signal1, NitzSignal signal2) {
+        long referenceTimeDifference = signal2.getAgeAdjustedElapsedRealtimeMillis()
+                - signal1.getAgeAdjustedElapsedRealtimeMillis();
+        long utcTimeDifference = signal2.getNitzData().getCurrentTimeInMillis()
+                - signal1.getNitzData().getCurrentTimeInMillis();
+        assertEquals(referenceTimeDifference, utcTimeDifference);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java
index c433bd0..3f2e4d5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java
@@ -19,6 +19,7 @@
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_AGE;
 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_SYSTEM_CLOCK_TIME;
 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO1;
 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.UNITED_KINGDOM_SCENARIO;
@@ -35,10 +36,9 @@
 
 import android.app.timedetector.TelephonyTimeSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.os.TimestampedValue;
 
 import com.android.internal.telephony.IndentingPrintWriter;
-import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzSignal;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nitz.NitzStateMachineImpl.NitzSignalInputFilterPredicate;
 import com.android.internal.telephony.nitz.NitzStateMachineTestSupport.FakeDeviceState;
@@ -105,8 +105,8 @@
     public void test_countryThenNitz() throws Exception {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
         String networkCountryIsoCode = scenario.getNetworkCountryIsoCode();
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
 
         // Capture expected results from the real suggester and confirm we can tell the difference
         // between them.
@@ -140,14 +140,14 @@
                 expectedTimeSuggestion, expectedTimeZoneSuggestion2);
 
         // Check NitzStateMachine exposed state.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
     }
 
     @Test
     public void test_nitzThenCountry() throws Exception {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
 
         String networkCountryIsoCode = scenario.getNetworkCountryIsoCode();
 
@@ -175,21 +175,21 @@
                 expectedTimeSuggestion, expectedTimeZoneSuggestion1);
 
         // Check NitzStateMachine exposed state.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
 
         // Simulate country being known and verify the behavior.
         script.countryReceived(networkCountryIsoCode)
                 .verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion2);
 
         // Check NitzStateMachine exposed state.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
     }
 
     @Test
     public void test_emptyCountryString_countryReceivedFirst() throws Exception {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
 
         Script script = new Script()
                 .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
@@ -224,14 +224,14 @@
                 expectedTimeSuggestion, expectedTimeZoneSuggestion);
 
         // Check NitzStateMachine exposed state.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
     }
 
     @Test
     public void test_emptyCountryStringUsTime_nitzReceivedFirst() throws Exception {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
 
         Script script = new Script()
                 .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
@@ -248,7 +248,7 @@
                 expectedTimeSuggestion, EMPTY_TIME_ZONE_SUGGESTION);
 
         // Check NitzStateMachine exposed state.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
 
         // Simulate an empty country being set.
         script.countryReceived("");
@@ -267,7 +267,7 @@
         script.verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion);
 
         // Check NitzStateMachine exposed state.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
     }
 
     @Test
@@ -281,8 +281,8 @@
 
         // Pre-flight: Simulate a device receiving signals that allow it to detect time and time
         // zone.
-        TimestampedValue<NitzData> preFlightNitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal preFlightNitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         TelephonyTimeSuggestion expectedPreFlightTimeSuggestion =
                 createTimeSuggestionFromNitzSignal(SLOT_INDEX, preFlightNitzSignal);
         String preFlightCountryIsoCode = scenario.getNetworkCountryIsoCode();
@@ -299,7 +299,7 @@
                 expectedPreFlightTimeSuggestion, expectedPreFlightTimeZoneSuggestion);
 
         // Check state that NitzStateMachine must expose.
-        assertEquals(preFlightNitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(preFlightNitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
 
         // Boarded flight: Airplane mode turned on / time zone detection still enabled.
         // The NitzStateMachine must lose all state and stop having an opinion about time zone.
@@ -347,8 +347,8 @@
 
         // Simulate the device receiving NITZ signal and country again after the flight. Now the
         // NitzStateMachine should be opinionated again.
-        TimestampedValue<NitzData> postFlightNitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal postFlightNitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         String postFlightCountryCode = scenario.getNetworkCountryIsoCode();
         script.countryReceived(postFlightCountryCode)
                 .nitzReceived(postFlightNitzSignal);
@@ -363,7 +363,7 @@
                 expectedPostFlightTimeSuggestion, expectedPostFlightTimeZoneSuggestion);
 
         // Check state that NitzStateMachine must expose.
-        assertEquals(postFlightNitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(postFlightNitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
     }
 
     /**
@@ -380,8 +380,8 @@
                 .networkAvailable();
 
         // Simulate a device receiving signals that allow it to detect time and time zone.
-        TimestampedValue<NitzData> initialNitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal initialNitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         TelephonyTimeSuggestion expectedInitialTimeSuggestion =
                 createTimeSuggestionFromNitzSignal(SLOT_INDEX, initialNitzSignal);
 
@@ -397,7 +397,7 @@
                 expectedInitialTimeSuggestion, expectedInitialTimeZoneSuggestion);
 
         // Check state that NitzStateMachine must expose.
-        assertEquals(initialNitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(initialNitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
 
         // Simulate the passage of time and update the device realtime clock.
         scenario.incrementTime(timeStepMillis);
@@ -433,8 +433,8 @@
 
         // Simulate the device receiving NITZ signal again. Now the NitzStateMachine should be
         // opinionated again.
-        TimestampedValue<NitzData> finalNitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal finalNitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
         script.nitzReceived(finalNitzSignal);
 
         // Verify the state machine did the right thing.
@@ -447,14 +447,14 @@
                 expectedFinalTimeSuggestion, expectedFinalTimeZoneSuggestion);
 
         // Check state that NitzStateMachine must expose.
-        assertEquals(finalNitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(finalNitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
     }
 
     @Test
     public void test_countryUnavailableClearsTimeZoneSuggestion() throws Exception {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
 
         Script script = new Script()
                 .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
@@ -478,7 +478,7 @@
                 expectedTimeSuggestion, expectedTimeZoneSuggestion2);
 
         // Check state that NitzStateMachine must expose.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
 
         // Simulate the country becoming unavailable and verify the state machine does the right
         // thing.
@@ -489,7 +489,7 @@
         script.verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion3);
 
         // Check state that NitzStateMachine must expose.
-        assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+        assertEquals(nitzSignal.getNitzData(), mNitzStateMachineImpl.getCachedNitzData());
     }
 
     /**
@@ -524,7 +524,7 @@
             return this;
         }
 
-        Script nitzReceived(TimestampedValue<NitzData> nitzSignal) {
+        Script nitzReceived(NitzSignal nitzSignal) {
             mNitzStateMachineImpl.handleNitzReceived(nitzSignal);
             return this;
         }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineTestSupport.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineTestSupport.java
index ed1c98b..6458517 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineTestSupport.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineTestSupport.java
@@ -23,9 +23,9 @@
 import android.icu.util.Calendar;
 import android.icu.util.GregorianCalendar;
 import android.icu.util.TimeZone;
-import android.os.TimestampedValue;
 
 import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzSignal;
 import com.android.internal.telephony.NitzStateMachine;
 import com.android.internal.telephony.NitzStateMachine.DeviceState;
 
@@ -34,9 +34,12 @@
  */
 final class NitzStateMachineTestSupport {
 
+    /** Used to indicate that a NitzSignal ageMillis is unimportant for the test. */
+    static final int ARBITRARY_AGE = 54321;
+
     // Values used to when initializing device state but where the value isn't important.
     static final long ARBITRARY_SYSTEM_CLOCK_TIME = createUtcTime(1977, 1, 1, 12, 0, 0);
-    static final long ARBITRARY_REALTIME_MILLIS = 123456789L;
+    static final long ARBITRARY_ELAPSED_REALTIME = 123456789L;
     static final String ARBITRARY_DEBUG_INFO = "Test debug info";
 
     // A country with a single zone : the zone can be guessed from the country.
@@ -120,9 +123,11 @@
             mNetworkCountryIsoCode = countryIsoCode;
         }
 
-        /** Creates an NITZ signal to match the scenario. */
-        TimestampedValue<NitzData> createNitzSignal(long elapsedRealtimeClock) {
-            return new TimestampedValue<>(elapsedRealtimeClock, createNitzData());
+        /**
+         * Creates an NITZ signal to match the scenario with the specified receipt / age properties.
+         */
+        NitzSignal createNitzSignal(long receiptElapsedMillis, long ageMillis) {
+            return new NitzSignal(receiptElapsedMillis, createNitzData(), ageMillis);
         }
 
         /** Creates an NITZ signal to match the scenario. */
@@ -214,7 +219,7 @@
             ignoreNitz = false;
             nitzUpdateDiffMillis = 2000;
             nitzUpdateSpacingMillis = 1000 * 60 * 10;
-            elapsedRealtime = ARBITRARY_REALTIME_MILLIS;
+            elapsedRealtime = ARBITRARY_ELAPSED_REALTIME;
         }
 
         @Override
@@ -222,18 +227,26 @@
             return nitzUpdateSpacingMillis;
         }
 
+        public void setNitzUpdateSpacingMillis(int nitzUpdateSpacingMillis) {
+            this.nitzUpdateSpacingMillis = nitzUpdateSpacingMillis;
+        }
+
         @Override
         public int getNitzUpdateDiffMillis() {
             return nitzUpdateDiffMillis;
         }
 
+        public void setNitzUpdateDiffMillis(int nitzUpdateDiffMillis) {
+            this.nitzUpdateDiffMillis = nitzUpdateDiffMillis;
+        }
+
         @Override
         public boolean getIgnoreNitz() {
             return ignoreNitz;
         }
 
         @Override
-        public long elapsedRealtime() {
+        public long elapsedRealtimeMillis() {
             return elapsedRealtime;
         }
 
@@ -275,20 +288,13 @@
     }
 
     static TelephonyTimeSuggestion createTimeSuggestionFromNitzSignal(
-            int slotIndex, TimestampedValue<NitzData> nitzSignal) {
+            int slotIndex, NitzSignal nitzSignal) {
         return new TelephonyTimeSuggestion.Builder(slotIndex)
-                .setUtcTime(createTimeSignalFromNitzSignal(nitzSignal))
+                .setUtcTime(nitzSignal.createTimeSignal())
                 .addDebugInfo("Test")
                 .build();
     }
 
-    private static TimestampedValue<Long> createTimeSignalFromNitzSignal(
-            TimestampedValue<NitzData> nitzSignal) {
-        return new TimestampedValue<>(
-                nitzSignal.getReferenceTimeMillis(),
-                nitzSignal.getValue().getCurrentTimeInMillis());
-    }
-
     private static TimeZone zone(String zoneId) {
         TimeZone timeZone = TimeZone.getFrozenTimeZone(zoneId);
         if (timeZone.getID().equals(TimeZone.UNKNOWN_ZONE_ID)) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneSuggesterImplTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneSuggesterImplTest.java
index 708d3de..5d59426 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneSuggesterImplTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneSuggesterImplTest.java
@@ -24,7 +24,8 @@
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
 
-import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_REALTIME_MILLIS;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_AGE;
+import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.ARBITRARY_ELAPSED_REALTIME;
 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.CZECHIA_SCENARIO;
 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID;
 import static com.android.internal.telephony.nitz.NitzStateMachineTestSupport.NEW_ZEALAND_DEFAULT_SCENARIO;
@@ -40,9 +41,9 @@
 import static org.junit.Assert.assertTrue;
 
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.os.TimestampedValue;
 
 import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzSignal;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nitz.NitzStateMachineImpl.TimeZoneSuggester;
 import com.android.internal.telephony.nitz.NitzStateMachineTestSupport.FakeDeviceState;
@@ -95,8 +96,8 @@
     @Test
     public void test_emptySuggestionForNullCountryWithNitz() throws Exception {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-        TimestampedValue<NitzData> nitzSignal =
-                scenario.createNitzSignal(ARBITRARY_REALTIME_MILLIS);
+        NitzSignal nitzSignal =
+                scenario.createNitzSignal(ARBITRARY_ELAPSED_REALTIME, ARBITRARY_AGE);
         assertEquals(EMPTY_TIME_ZONE_SUGGESTION,
                 mTimeZoneSuggester.getTimeZoneSuggestion(
                         SLOT_INDEX, null /* countryIsoCode */, nitzSignal));
@@ -137,9 +138,10 @@
         // NITZ with a "" country code is interpreted as a test network so only offset is used
         // to get a match.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, "" /* countryIsoCode */,
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, "" /* countryIsoCode */, nitzSignal);
             assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
             assertEquals(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY, actualSuggestion.getMatchType());
             assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
@@ -147,9 +149,10 @@
 
         // NITZ alone is not enough to get a result when the country is not available.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, null /* countryIsoCode */,
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, null /* countryIsoCode */, nitzSignal);
             assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
         }
 
@@ -161,9 +164,10 @@
                             .setMatchType(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET)
                             .setQuality(QUALITY_SINGLE_ZONE)
                             .build();
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
             assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
         }
 
@@ -171,12 +175,11 @@
         // since there are multiple zones to choose from.
         {
             // We use an NITZ from CZ to generate an NITZ signal with a bad offset.
-            TimestampedValue<NitzData> badNitzSignal =
-                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal badNitzSignal = CZECHIA_SCENARIO.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion = EMPTY_TIME_ZONE_SUGGESTION;
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    badNitzSignal);
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), badNitzSignal);
             assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
         }
     }
@@ -209,9 +212,10 @@
         // NITZ with a "" country code is interpreted as a test network so only offset is used
         // to get a match.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, "" /* countryIsoCode */,
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, "" /* countryIsoCode */, nitzSignal);
             assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
             assertEquals(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY, actualSuggestion.getMatchType());
             assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
@@ -219,17 +223,19 @@
 
         // NITZ alone is not enough to get a result when the country is not available.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, null /* countryIsoCode */,
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, null /* countryIsoCode */, nitzSignal);
             assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
         }
 
         // Country + NITZ is not enough for a unique time zone detection result for this scenario.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
             assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
             assertEquals(MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, actualSuggestion.getMatchType());
             assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
@@ -241,12 +247,11 @@
         // since there are multiple zones to choose from.
         {
             // We use an NITZ from CZ to generate an NITZ signal with a bad offset.
-            TimestampedValue<NitzData> badNitzSignal =
-                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal badNitzSignal = CZECHIA_SCENARIO.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion = EMPTY_TIME_ZONE_SUGGESTION;
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    badNitzSignal);
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), badNitzSignal);
             assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
         }
     }
@@ -278,9 +283,10 @@
         // NITZ with a "" country code is interpreted as a test network so only offset is used
         // to get a match.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, "" /* countryIsoCode */,
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, "" /* countryIsoCode */, nitzSignal);
             assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
             assertEquals(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY, actualSuggestion.getMatchType());
             assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
@@ -289,9 +295,10 @@
 
         // NITZ alone is not enough to get a result when the country is not available.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, null /* countryIsoCode */,
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, null /* countryIsoCode */, nitzSignal);
             assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
         }
 
@@ -304,9 +311,10 @@
                             .setQuality(QUALITY_SINGLE_ZONE)
                             .build();
 
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
             assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
         }
 
@@ -314,8 +322,8 @@
         // there's only one zone.
         {
             // We use an NITZ from Czechia to generate an NITZ signal with a bad offset.
-            TimestampedValue<NitzData> badNitzSignal =
-                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal badNitzSignal = CZECHIA_SCENARIO.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
                     new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
                             .setZoneId(scenario.getTimeZoneId())
@@ -324,8 +332,7 @@
                             .build();
 
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    badNitzSignal);
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), badNitzSignal);
             assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
         }
     }
@@ -356,21 +363,22 @@
         // NITZ with a "" country code is interpreted as a test network so only offset is used
         // to get a match.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion =
                     mTimeZoneSuggester.getTimeZoneSuggestion(
-                            SLOT_INDEX, "" /* countryIsoCode */,
-                            scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                            SLOT_INDEX, "" /* countryIsoCode */, nitzSignal);
             assertEquals(SLOT_INDEX, actualSuggestion.getSlotIndex());
             assertEquals(MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY, actualSuggestion.getMatchType());
             assertEquals(QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET, actualSuggestion.getQuality());
-
         }
 
         // NITZ alone is not enough to get a result when the country is not available.
         {
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, null /* countryIsoCode */,
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, null /* countryIsoCode */, nitzSignal);
             assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
         }
 
@@ -383,9 +391,10 @@
                             .setQuality(QUALITY_SINGLE_ZONE)
                             .build();
 
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);
             assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
         }
 
@@ -393,8 +402,8 @@
         // there's only one zone.
         {
             // We use an NITZ from the US to generate an NITZ signal with a bad offset.
-            TimestampedValue<NitzData> badNitzSignal =
-                    UNIQUE_US_ZONE_SCENARIO1.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal badNitzSignal = UNIQUE_US_ZONE_SCENARIO1.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
                     new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
                             .setZoneId(scenario.getTimeZoneId())
@@ -403,8 +412,7 @@
                             .build();
 
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
-                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(),
-                    badNitzSignal);
+                    SLOT_INDEX, scenario.getNetworkCountryIsoCode(), badNitzSignal);
             assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
         }
     }
@@ -430,14 +438,15 @@
         // NITZ + bogus NITZ is not enough to get a result.
         {
             // Create a corrupted NITZ signal, where the offset information has been lost.
-            TimestampedValue<NitzData> goodNitzSignal =
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal goodNitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             NitzData bogusNitzData = NitzData.createForTests(
                     0 /* UTC! */, null /* dstOffsetMillis */,
-                    goodNitzSignal.getValue().getCurrentTimeInMillis(),
+                    goodNitzSignal.getNitzData().getCurrentTimeInMillis(),
                     null /* emulatorHostTimeZone */);
-            TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
-                    goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
+            NitzSignal badNitzSignal = new NitzSignal(
+                    goodNitzSignal.getReceiptElapsedRealtimeMillis(), bogusNitzData,
+                    goodNitzSignal.getAgeMillis());
 
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
                     SLOT_INDEX, scenario.getNetworkCountryIsoCode(), badNitzSignal);
@@ -466,14 +475,15 @@
         // NITZ + bogus NITZ is not enough to get a result.
         {
             // Create a corrupted NITZ signal, where the offset information has been lost.
-            TimestampedValue<NitzData> goodNitzSignal =
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal goodNitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             NitzData bogusNitzData = NitzData.createForTests(
                     0 /* UTC! */, null /* dstOffsetMillis */,
-                    goodNitzSignal.getValue().getCurrentTimeInMillis(),
+                    goodNitzSignal.getNitzData().getCurrentTimeInMillis(),
                     null /* emulatorHostTimeZone */);
-            TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
-                    goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
+            NitzSignal badNitzSignal = new NitzSignal(
+                    goodNitzSignal.getReceiptElapsedRealtimeMillis(), bogusNitzData,
+                    goodNitzSignal.getAgeMillis());
 
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
                     SLOT_INDEX, scenario.getNetworkCountryIsoCode(), badNitzSignal);
@@ -485,11 +495,11 @@
     public void test_emulatorNitzExtensionUsedForTimeZone() throws Exception {
         Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
 
-        TimestampedValue<NitzData> originalNitzSignal =
-                scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+        NitzSignal originalNitzSignal = scenario.createNitzSignal(
+                mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
 
         // Create an NITZ signal with an explicit time zone (as can happen on emulators).
-        NitzData originalNitzData = originalNitzSignal.getValue();
+        NitzData originalNitzData = originalNitzSignal.getNitzData();
 
         // A time zone that is obviously not in the US, but because the explicit value is present it
         // should not be questioned.
@@ -499,8 +509,9 @@
                 originalNitzData.getDstAdjustmentMillis(),
                 originalNitzData.getCurrentTimeInMillis(),
                 java.util.TimeZone.getTimeZone(emulatorTimeZoneId) /* emulatorHostTimeZone */);
-        TimestampedValue<NitzData> emulatorNitzSignal = new TimestampedValue<>(
-                originalNitzSignal.getReferenceTimeMillis(), emulatorNitzData);
+        NitzSignal emulatorNitzSignal = new NitzSignal(
+                originalNitzSignal.getReceiptElapsedRealtimeMillis(), emulatorNitzData,
+                originalNitzSignal.getAgeMillis());
 
         TelephonyTimeZoneSuggestion expectedTimeZoneSuggestion =
                 new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
@@ -535,8 +546,8 @@
         // Confirm what happens when NITZ is correct for the country default.
         {
             Scenario scenario = NEW_ZEALAND_DEFAULT_SCENARIO;
-            TimestampedValue<NitzData> nitzSignal =
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedSuggestion =
                     new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
                             .setZoneId(scenario.getTimeZoneId())
@@ -552,8 +563,8 @@
         // A valid NITZ signal for the non-default zone should still be correctly detected.
         {
             Scenario scenario = NEW_ZEALAND_OTHER_SCENARIO;
-            TimestampedValue<NitzData> nitzSignal =
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedSuggestion =
                     new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
                             .setZoneId(scenario.getTimeZoneId())
@@ -571,8 +582,8 @@
         {
             Scenario scenario = NEW_ZEALAND_DEFAULT_SCENARIO;
             // Use a scenario that has a different offset than NZ to generate the NITZ signal.
-            TimestampedValue<NitzData> nitzSignal =
-                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal nitzSignal = CZECHIA_SCENARIO.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedSuggestion =
                     new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
                             .setZoneId(NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID)
@@ -607,8 +618,8 @@
         // Confirm what happens when NITZ is correct for the country default.
         {
             Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
-            TimestampedValue<NitzData> nitzSignal =
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedSuggestion =
                     new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
                             .setZoneId(scenario.getTimeZoneId())
@@ -624,8 +635,8 @@
         // A valid NITZ signal for the non-default zone should still be correctly detected.
         {
             Scenario scenario = UNIQUE_US_ZONE_SCENARIO2;
-            TimestampedValue<NitzData> nitzSignal =
-                    scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal nitzSignal = scenario.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedSuggestion =
                     new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX)
                             .setZoneId(scenario.getTimeZoneId())
@@ -644,8 +655,8 @@
             // A scenario that has a different offset than US.
             Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
             // Use a scenario that has a different offset than the US to generate the NITZ signal.
-            TimestampedValue<NitzData> nitzSignal =
-                    CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+            NitzSignal nitzSignal = CZECHIA_SCENARIO.createNitzSignal(
+                    mFakeDeviceState.elapsedRealtimeMillis(), ARBITRARY_AGE);
             TelephonyTimeZoneSuggestion expectedSuggestion = EMPTY_TIME_ZONE_SUGGESTION;
             TelephonyTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
                     SLOT_INDEX, scenario.getNetworkCountryIsoCode(), nitzSignal);